47bdcb11d8
## Context Moving isActive filtering to the frontend for page layout tabs and widgets, hiding inactive entities from the UI while keeping them in state for future reactivation Next we will implement deactivated standard tab re-activation during tab creation (cc @Devessier) <img width="234" height="303" alt="📋 Menu (Slots)" src="https://github.com/user-attachments/assets/17a25ac6-55e2-4778-b7f0-e7554ed69704" />
534 lines
15 KiB
TypeScript
534 lines
15 KiB
TypeScript
import {
|
|
VIEW_FIELD_GQL_FIELDS,
|
|
VIEW_FIELD_GROUP_GQL_FIELDS,
|
|
VIEW_GQL_FIELDS,
|
|
} from 'test/integration/constants/view-gql-fields.constants';
|
|
import { findViewFieldGroups } from 'test/integration/metadata/suites/view-field-group/utils/find-view-field-groups.util';
|
|
import { upsertFieldsWidget } from 'test/integration/metadata/suites/view-field-group/utils/upsert-fields-widget.util';
|
|
import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
type FieldsWidgetTestSetup = {
|
|
widgetId: string;
|
|
viewId: string;
|
|
labelIdentifierFieldMetadataId: string | null;
|
|
viewFields: Array<{
|
|
id: string;
|
|
fieldMetadataId: string;
|
|
position: number;
|
|
isVisible: boolean;
|
|
viewFieldGroupId: string | null;
|
|
}>;
|
|
};
|
|
|
|
const VIEW_WITH_FIELDS_AND_GROUPS_GQL_FIELDS = `
|
|
${VIEW_GQL_FIELDS}
|
|
viewFields {
|
|
${VIEW_FIELD_GQL_FIELDS}
|
|
}
|
|
viewFieldGroups {
|
|
${VIEW_FIELD_GROUP_GQL_FIELDS}
|
|
}
|
|
`;
|
|
|
|
const fetchFieldsWidgetTestSetup = async (): Promise<FieldsWidgetTestSetup> => {
|
|
// TODO refactor should not use global source for that
|
|
const widgets = await global.testDataSource.query(
|
|
`SELECT id, configuration->>'viewId' AS "viewId"
|
|
FROM core."pageLayoutWidget"
|
|
WHERE type = 'FIELDS'
|
|
AND "deletedAt" IS NULL
|
|
LIMIT 1`,
|
|
);
|
|
|
|
expect(widgets.length).toBeGreaterThan(0);
|
|
|
|
const { id: widgetId, viewId } = widgets[0];
|
|
|
|
expect(widgetId).toBeDefined();
|
|
expect(viewId).toBeDefined();
|
|
|
|
// TODO refactor should not use global source for that
|
|
const views = await global.testDataSource.query(
|
|
`SELECT v."objectMetadataId", om."labelIdentifierFieldMetadataId"
|
|
FROM core."view" v
|
|
JOIN core."objectMetadata" om ON om.id = v."objectMetadataId"
|
|
WHERE v.id = $1`,
|
|
[viewId],
|
|
);
|
|
|
|
const labelIdentifierFieldMetadataId =
|
|
views[0]?.labelIdentifierFieldMetadataId ?? null;
|
|
|
|
const { data } = await findViewFields({
|
|
viewId,
|
|
gqlFields: 'id fieldMetadataId position isVisible viewFieldGroupId',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const viewFields = data.getViewFields;
|
|
|
|
return { widgetId, viewId, labelIdentifierFieldMetadataId, viewFields };
|
|
};
|
|
|
|
describe('upsertFieldsWidget', () => {
|
|
let testSetup: FieldsWidgetTestSetup;
|
|
|
|
beforeAll(async () => {
|
|
testSetup = await fetchFieldsWidgetTestSetup();
|
|
});
|
|
|
|
describe('with groups input', () => {
|
|
it('should upsert fields widget with a new group and return a view', async () => {
|
|
const newGroupId = uuidv4();
|
|
const targetFields = testSetup.viewFields.slice(0, 2);
|
|
|
|
const { data, errors } = await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: newGroupId,
|
|
name: 'Test Group',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: targetFields.map((f, index) => ({
|
|
viewFieldId: f.id,
|
|
isVisible: true,
|
|
position: index,
|
|
})),
|
|
},
|
|
],
|
|
},
|
|
gqlFields: VIEW_WITH_FIELDS_AND_GROUPS_GQL_FIELDS,
|
|
});
|
|
|
|
expect(errors).toBeUndefined();
|
|
expect(data.upsertFieldsWidget).toBeDefined();
|
|
expect(data.upsertFieldsWidget.id).toBeDefined();
|
|
expect(data.upsertFieldsWidget.name).toBeDefined();
|
|
|
|
const { data: groupsData } = await findViewFieldGroups({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id name',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const createdGroup = groupsData.getViewFieldGroups.find(
|
|
(g: { id: string }) => g.id === newGroupId,
|
|
);
|
|
|
|
expect(createdGroup).toBeDefined();
|
|
expect(createdGroup!.name).toBe('Test Group');
|
|
});
|
|
|
|
it('should hard-delete groups not included in the input', async () => {
|
|
// First, create a group via upsert
|
|
const groupToDeleteId = uuidv4();
|
|
const groupToKeepId = uuidv4();
|
|
|
|
const twoFields = testSetup.viewFields.slice(0, 2);
|
|
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: groupToDeleteId,
|
|
name: 'Group To Delete',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: twoFields[0].id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: groupToKeepId,
|
|
name: 'Group To Keep',
|
|
position: 1,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: twoFields[1].id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Now upsert again without the first group
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: groupToKeepId,
|
|
name: 'Group To Keep',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: twoFields[1].id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Verify the omitted group was hard-deleted (row should be completely gone)
|
|
const deletedGroup = await global.testDataSource.query(
|
|
`SELECT id FROM core."viewFieldGroup"
|
|
WHERE id = $1`,
|
|
[groupToDeleteId],
|
|
);
|
|
|
|
expect(deletedGroup.length).toBe(0);
|
|
|
|
// Verify the kept group is still active
|
|
const { data: keptGroupData } = await findViewFieldGroups({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const keptGroup = keptGroupData.getViewFieldGroups.find(
|
|
(g: { id: string }) => g.id === groupToKeepId,
|
|
);
|
|
|
|
expect(keptGroup).toBeDefined();
|
|
});
|
|
|
|
it('should update view field positions and visibility within groups', async () => {
|
|
const groupId = uuidv4();
|
|
const targetField = testSetup.viewFields.find(
|
|
(field) =>
|
|
field.fieldMetadataId !== testSetup.labelIdentifierFieldMetadataId,
|
|
)!;
|
|
|
|
expect(targetField).toBeDefined();
|
|
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: groupId,
|
|
name: 'Position Test Group',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: false,
|
|
position: 42,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Verify the view field was updated
|
|
const { data: fieldsData } = await findViewFields({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id isVisible position viewFieldGroupId',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const updatedField = fieldsData.getViewFields.find(
|
|
(f: { id: string }) => f.id === targetField.id,
|
|
);
|
|
|
|
expect(updatedField).toBeDefined();
|
|
expect(updatedField!.isVisible).toBe(false);
|
|
expect(updatedField!.position).toBe(42);
|
|
expect(updatedField!.viewFieldGroupId).toBe(groupId);
|
|
});
|
|
});
|
|
|
|
describe('with fields input (ungrouped)', () => {
|
|
it('should upsert fields widget with flat fields and return a view', async () => {
|
|
const targetFields = testSetup.viewFields.slice(0, 3);
|
|
|
|
const { data, errors } = await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
fields: targetFields.map((f, index) => ({
|
|
viewFieldId: f.id,
|
|
isVisible: true,
|
|
position: index,
|
|
})),
|
|
},
|
|
gqlFields: VIEW_WITH_FIELDS_AND_GROUPS_GQL_FIELDS,
|
|
});
|
|
|
|
expect(errors).toBeUndefined();
|
|
expect(data.upsertFieldsWidget).toBeDefined();
|
|
expect(data.upsertFieldsWidget.id).toBeDefined();
|
|
});
|
|
|
|
it('should hard-delete all existing groups when using flat fields', async () => {
|
|
// First create a group via upsert
|
|
const groupId = uuidv4();
|
|
const targetField = testSetup.viewFields[0];
|
|
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: groupId,
|
|
name: 'Group To Be Deleted',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Verify group exists
|
|
const { data: groupBeforeData } = await findViewFieldGroups({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const groupBefore = groupBeforeData.getViewFieldGroups.find(
|
|
(g: { id: string }) => g.id === groupId,
|
|
);
|
|
|
|
expect(groupBefore).toBeDefined();
|
|
|
|
// Now upsert with flat fields
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Verify custom group is hard-deleted (not just deactivated)
|
|
const { data: allGroupsData } = await findViewFieldGroups({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id isActive',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const specificGroup = allGroupsData.getViewFieldGroups.find(
|
|
(g: { id: string }) => g.id === groupId,
|
|
);
|
|
|
|
expect(specificGroup).toBeUndefined();
|
|
|
|
// Verify the field's viewFieldGroupId is null
|
|
const { data: updatedFieldData } = await findViewFields({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id viewFieldGroupId',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const updatedField = updatedFieldData.getViewFields.find(
|
|
(f: { id: string }) => f.id === targetField.id,
|
|
);
|
|
|
|
expect(updatedField).toBeDefined();
|
|
expect(updatedField!.viewFieldGroupId).toBeNull();
|
|
});
|
|
|
|
it('should update field positions and visibility without groups', async () => {
|
|
const targetField = testSetup.viewFields.find(
|
|
(field) =>
|
|
field.fieldMetadataId !== testSetup.labelIdentifierFieldMetadataId,
|
|
)!;
|
|
|
|
expect(targetField).toBeDefined();
|
|
|
|
const groupId = uuidv4();
|
|
|
|
// First assign the field to a group so it has a non-null viewFieldGroupId
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: groupId,
|
|
name: 'Temporary Group',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Now switch to flat fields with different position and visibility
|
|
await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: false,
|
|
position: 99,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const { data: updatedFieldData } = await findViewFields({
|
|
viewId: testSetup.viewId,
|
|
gqlFields: 'id isVisible position viewFieldGroupId',
|
|
expectToFail: false,
|
|
});
|
|
|
|
const updatedField = updatedFieldData.getViewFields.find(
|
|
(f: { id: string }) => f.id === targetField.id,
|
|
);
|
|
|
|
expect(updatedField).toBeDefined();
|
|
expect(updatedField!.isVisible).toBe(false);
|
|
expect(updatedField!.position).toBe(99);
|
|
expect(updatedField!.viewFieldGroupId).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('validation', () => {
|
|
it('should fail when both groups and fields are provided', async () => {
|
|
const targetField = testSetup.viewFields[0];
|
|
|
|
const { errors } = await upsertFieldsWidget({
|
|
expectToFail: true,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
groups: [
|
|
{
|
|
id: uuidv4(),
|
|
name: 'Test',
|
|
position: 0,
|
|
isVisible: true,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(errors).toBeDefined();
|
|
expect(errors.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should fail when neither groups nor fields are provided', async () => {
|
|
const { errors } = await upsertFieldsWidget({
|
|
expectToFail: true,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
},
|
|
});
|
|
|
|
expect(errors).toBeDefined();
|
|
expect(errors.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should fail when widget id does not exist', async () => {
|
|
const { errors } = await upsertFieldsWidget({
|
|
expectToFail: true,
|
|
input: {
|
|
widgetId: uuidv4(),
|
|
fields: [
|
|
{
|
|
viewFieldId: testSetup.viewFields[0].id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(errors).toBeDefined();
|
|
expect(errors.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('return type', () => {
|
|
it('should return a view with the expected fields', async () => {
|
|
const targetField = testSetup.viewFields[0];
|
|
|
|
const { data } = await upsertFieldsWidget({
|
|
expectToFail: false,
|
|
input: {
|
|
widgetId: testSetup.widgetId,
|
|
fields: [
|
|
{
|
|
viewFieldId: targetField.id,
|
|
isVisible: true,
|
|
position: 0,
|
|
},
|
|
],
|
|
},
|
|
gqlFields: VIEW_WITH_FIELDS_AND_GROUPS_GQL_FIELDS,
|
|
});
|
|
|
|
const view = data.upsertFieldsWidget;
|
|
|
|
expect(view.id).toBeDefined();
|
|
expect(view.name).toBeDefined();
|
|
expect(view.objectMetadataId).toBeDefined();
|
|
expect(view.workspaceId).toBeDefined();
|
|
expect(view.createdAt).toBeDefined();
|
|
expect(view.updatedAt).toBeDefined();
|
|
expect(Array.isArray(view.viewFields)).toBe(true);
|
|
expect(Array.isArray(view.viewFieldGroups)).toBe(true);
|
|
});
|
|
});
|
|
});
|