Add is active to overridable entities and deactivation logic for page layouts (#19200)
- Replace soft-deletion (deletedAt) with isActive boolean for overridable entities (tabs, widgets, viewFieldGroups, viewFields). Standard entities are deactivated (isActive: false) when removed from update payloads, while custom entities are hard-deleted. - When a viewFieldGroup is deactivated/deleted, its viewFields are reassigned to the next section by position (or null if none remain). - Add isActive: true filters to viewFieldGroup and viewField API queries so deactivated entities are excluded from responses. Next: - fields-widget-upsert.service.ts should be refactored a bit - Add restore logic
This commit is contained in:
+1
@@ -782,6 +782,7 @@ export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveO
|
||||
},
|
||||
objectMetadataId: object.id,
|
||||
objectMetadataUniversalIdentifier: object.universalIdentifier,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddIsActiveToOverridableEntities1774966727625
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddIsActiveToOverridableEntities1774966727625';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFieldGroup" ADD "isActive" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ADD "isActive" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" ADD "isActive" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" ADD "isActive" boolean NOT NULL DEFAULT true`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."viewFieldGroup" SET "isActive" = false WHERE "deletedAt" IS NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."viewField" SET "isActive" = false WHERE "deletedAt" IS NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."pageLayoutTab" SET "isActive" = false WHERE "deletedAt" IS NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`UPDATE "core"."pageLayoutWidget" SET "isActive" = false WHERE "deletedAt" IS NOT NULL`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutWidget" DROP COLUMN "isActive"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."pageLayoutTab" DROP COLUMN "isActive"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" DROP COLUMN "isActive"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewFieldGroup" DROP COLUMN "isActive"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
@@ -23,6 +23,7 @@ export const fromPageLayoutTabManifestToUniversalFlatPageLayoutTab = ({
|
||||
icon: pageLayoutTabManifest.icon ?? null,
|
||||
layoutMode:
|
||||
pageLayoutTabManifest.layoutMode ?? PageLayoutTabLayoutMode.GRID,
|
||||
isActive: true,
|
||||
widgetUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ export const fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget = ({
|
||||
applicationUniversalIdentifier,
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
title: pageLayoutWidgetManifest.title,
|
||||
isActive: true,
|
||||
type: pageLayoutWidgetManifest.type as WidgetType,
|
||||
objectMetadataUniversalIdentifier:
|
||||
pageLayoutWidgetManifest.objectUniversalIdentifier ?? null,
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ export const fromViewFieldGroupManifestToUniversalFlatViewFieldGroup = ({
|
||||
name: viewFieldGroupManifest.name ?? '',
|
||||
position: viewFieldGroupManifest.position,
|
||||
isVisible: viewFieldGroupManifest.isVisible ?? true,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
createdAt: now,
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ export const fromViewFieldManifestToUniversalFlatViewField = ({
|
||||
viewFieldGroupUniversalIdentifier:
|
||||
viewFieldManifest.viewFieldGroupUniversalIdentifier ?? null,
|
||||
isVisible: viewFieldManifest.isVisible ?? true,
|
||||
isActive: true,
|
||||
size: viewFieldManifest.size ?? 0,
|
||||
position: viewFieldManifest.position,
|
||||
aggregateOperation: viewFieldManifest.aggregateOperation ?? null,
|
||||
|
||||
@@ -480,7 +480,11 @@ export class DataloaderService {
|
||||
flatEntityIds: flatView.viewFieldGroupIds,
|
||||
flatEntityMaps: flatViewFieldGroupMaps,
|
||||
})
|
||||
.filter((flatViewFieldGroup) => flatViewFieldGroup.deletedAt === null)
|
||||
.filter(
|
||||
(flatViewFieldGroup) =>
|
||||
flatViewFieldGroup.deletedAt === null &&
|
||||
flatViewFieldGroup.isActive,
|
||||
)
|
||||
.map(fromFlatViewFieldGroupToViewFieldGroupDto);
|
||||
});
|
||||
});
|
||||
@@ -509,7 +513,11 @@ export class DataloaderService {
|
||||
for (const flatViewField of Object.values(
|
||||
flatViewFieldMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(flatViewField) || flatViewField.deletedAt !== null) {
|
||||
if (
|
||||
!isDefined(flatViewField) ||
|
||||
flatViewField.deletedAt !== null ||
|
||||
!flatViewField.isActive
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -575,7 +583,10 @@ export class DataloaderService {
|
||||
flatEntityIds: flatView.viewFieldIds,
|
||||
flatEntityMaps: flatViewFieldMaps,
|
||||
})
|
||||
.filter((flatViewField) => flatViewField.deletedAt === null)
|
||||
.filter(
|
||||
(flatViewField) =>
|
||||
flatViewField.deletedAt === null && flatViewField.isActive,
|
||||
)
|
||||
.map(fromFlatViewFieldToViewFieldDto);
|
||||
});
|
||||
});
|
||||
|
||||
+4
@@ -174,6 +174,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"position",
|
||||
"deletedAt",
|
||||
"icon",
|
||||
"isActive",
|
||||
"overrides",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
@@ -190,6 +191,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"universalConfiguration",
|
||||
"deletedAt",
|
||||
"conditionalDisplay",
|
||||
"isActive",
|
||||
"overrides",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
@@ -299,6 +301,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"aggregateOperation",
|
||||
"viewFieldGroupUniversalIdentifier",
|
||||
"deletedAt",
|
||||
"isActive",
|
||||
"universalOverrides",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
@@ -310,6 +313,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"name",
|
||||
"position",
|
||||
"isVisible",
|
||||
"isActive",
|
||||
"deletedAt",
|
||||
"overrides",
|
||||
],
|
||||
|
||||
+24
@@ -373,6 +373,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toCompare: true,
|
||||
isOverridable: true,
|
||||
},
|
||||
isActive: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: false,
|
||||
},
|
||||
deletedAt: {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
@@ -455,6 +461,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toStringify: false,
|
||||
universalProperty: 'viewUniversalIdentifier',
|
||||
},
|
||||
isActive: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: false,
|
||||
},
|
||||
overrides: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
@@ -916,6 +928,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
universalProperty: undefined,
|
||||
isOverridable: true,
|
||||
},
|
||||
isActive: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: false,
|
||||
},
|
||||
overrides: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
@@ -966,6 +984,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
isActive: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
isOverridable: false,
|
||||
},
|
||||
overrides: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
type EntityWithApplicationIdentifier = {
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
export const splitEntitiesByRemovalStrategy = <
|
||||
T extends EntityWithApplicationIdentifier,
|
||||
>({
|
||||
entitiesToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
entitiesToRemove: T[];
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
now: string;
|
||||
}): {
|
||||
toHardDelete: T[];
|
||||
toDeactivate: (T & { isActive: false; updatedAt: string })[];
|
||||
} => {
|
||||
const toHardDelete: T[] = [];
|
||||
const toDeactivate: (T & { isActive: false; updatedAt: string })[] = [];
|
||||
|
||||
for (const entity of entitiesToRemove) {
|
||||
if (
|
||||
entity.applicationUniversalIdentifier ===
|
||||
workspaceCustomApplicationUniversalIdentifier
|
||||
) {
|
||||
toHardDelete.push(entity);
|
||||
} else {
|
||||
toDeactivate.push({
|
||||
...entity,
|
||||
isActive: false as const,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { toHardDelete, toDeactivate };
|
||||
};
|
||||
+1
@@ -70,6 +70,7 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
|
||||
updatedLabelIdentifierFieldMetadata.universalIdentifier,
|
||||
position: lowestViewFieldPosition - 1,
|
||||
isVisible: true,
|
||||
isActive: true,
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
viewId: flatView.id,
|
||||
viewUniversalIdentifier: flatView.universalIdentifier,
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({
|
||||
id: pageLayoutTabId,
|
||||
title: createPageLayoutTabInput.title,
|
||||
position: createPageLayoutTabInput.position ?? 0,
|
||||
isActive: true,
|
||||
pageLayoutId: createPageLayoutTabInput.pageLayoutId,
|
||||
pageLayoutUniversalIdentifier,
|
||||
workspaceId,
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ export const transformPageLayoutTabEntityToFlatPageLayoutTab = ({
|
||||
id: pageLayoutTabEntity.id,
|
||||
title: pageLayoutTabEntity.title,
|
||||
position: pageLayoutTabEntity.position,
|
||||
isActive: pageLayoutTabEntity.isActive,
|
||||
pageLayoutId: pageLayoutTabEntity.pageLayoutId,
|
||||
workspaceId: pageLayoutTabEntity.workspaceId,
|
||||
universalIdentifier: pageLayoutTabEntity.universalIdentifier,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
|
||||
return {
|
||||
id: pageLayoutWidgetId,
|
||||
...commonProperties,
|
||||
isActive: true,
|
||||
workspaceId,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
|
||||
+2
-7
@@ -25,9 +25,7 @@ export const reconstructFlatPageLayoutWithTabsAndWidgets = ({
|
||||
}): FlatPageLayoutWithTabsAndWidgets => {
|
||||
const tabs = Object.values(flatPageLayoutTabMaps.byUniversalIdentifier)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(tab) => tab.pageLayoutId === layout.id && !isDefined(tab.deletedAt),
|
||||
)
|
||||
.filter((tab) => tab.pageLayoutId === layout.id && tab.isActive)
|
||||
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
|
||||
|
||||
const tabsWithWidgets: FlatPageLayoutTabWithWidgets[] = tabs.map((tab) => {
|
||||
@@ -35,10 +33,7 @@ export const reconstructFlatPageLayoutWithTabsAndWidgets = ({
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
widget.pageLayoutTabId === tab.id && !isDefined(widget.deletedAt),
|
||||
);
|
||||
.filter((widget) => widget.pageLayoutTabId === tab.id && widget.isActive);
|
||||
|
||||
return {
|
||||
...tab,
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ export const fromCreateViewFieldGroupInputToFlatViewFieldGroupToCreate = ({
|
||||
universalIdentifier: createViewFieldGroupInput.universalIdentifier ?? v4(),
|
||||
position: createViewFieldGroupInput.position ?? 0,
|
||||
isVisible: createViewFieldGroupInput.isVisible ?? true,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
|
||||
+20
-19
@@ -53,6 +53,7 @@ const buildFlatViewFieldGroupMaps = (
|
||||
id: entry.id,
|
||||
viewId: entry.viewId ?? VIEW_ID,
|
||||
position: entry.position ?? 0,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
]),
|
||||
@@ -69,7 +70,7 @@ const buildFlatViewFieldMaps = (
|
||||
viewId: string;
|
||||
viewFieldGroupId: string | null;
|
||||
position: number;
|
||||
deletedAt: string | null;
|
||||
isActive: boolean;
|
||||
}[] = [],
|
||||
): FlatViewFieldMaps =>
|
||||
({
|
||||
@@ -85,18 +86,18 @@ const buildFieldsWidget = ({
|
||||
objectMetadataUniversalIdentifier = OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
viewId = VIEW_ID,
|
||||
isVisible = true,
|
||||
deletedAt = null as string | null,
|
||||
isActive = true,
|
||||
}: {
|
||||
widgetUniversalIdentifier?: string;
|
||||
objectMetadataUniversalIdentifier?: string;
|
||||
viewId?: string | null;
|
||||
isVisible?: boolean;
|
||||
deletedAt?: string | null;
|
||||
isActive?: boolean;
|
||||
} = {}) => ({
|
||||
universalIdentifier: widgetUniversalIdentifier,
|
||||
objectMetadataUniversalIdentifier,
|
||||
type: WidgetType.FIELDS,
|
||||
deletedAt,
|
||||
isActive,
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELDS,
|
||||
viewId,
|
||||
@@ -178,7 +179,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should skip deleted widgets', () => {
|
||||
it('should skip inactive widgets', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
@@ -188,7 +189,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
|
||||
buildFieldsWidget({ deletedAt: '2024-01-01T00:00:00.000Z' }),
|
||||
buildFieldsWidget({ isActive: false }),
|
||||
]),
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -349,14 +350,14 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 3,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'existing-vf-2',
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 7,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -397,7 +398,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 2,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -413,7 +414,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
expect(result[2].position).toBe(5);
|
||||
});
|
||||
|
||||
it('should ignore deleted view fields when computing position', () => {
|
||||
it('should ignore inactive view fields when computing position', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
@@ -431,14 +432,14 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 5,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'deleted-vf',
|
||||
universalIdentifier: 'inactive-vf',
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 99,
|
||||
deletedAt: '2024-01-01T00:00:00.000Z',
|
||||
isActive: false,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -469,7 +470,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: 'other-view-db-id',
|
||||
viewFieldGroupId: null,
|
||||
position: 50,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -556,21 +557,21 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: VIEW_FIELD_GROUP_ID,
|
||||
position: 2,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'vf-no-group',
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 99,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'vf-other-group',
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: 'other-group-id',
|
||||
position: 50,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
@@ -606,14 +607,14 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: null,
|
||||
position: 1,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
universalIdentifier: 'vf-in-group',
|
||||
viewId: VIEW_ID,
|
||||
viewFieldGroupId: VIEW_FIELD_GROUP_ID,
|
||||
position: 99,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
},
|
||||
]),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
|
||||
+4
-3
@@ -39,7 +39,7 @@ const getMatchingFieldsWidgets = ({
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
!isDefined(widget.deletedAt) &&
|
||||
widget.isActive &&
|
||||
widget.type === WidgetType.FIELDS &&
|
||||
widget.objectMetadataUniversalIdentifier ===
|
||||
objectMetadataUniversalIdentifier &&
|
||||
@@ -59,7 +59,7 @@ const findLastViewFieldGroupId = ({
|
||||
flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((group) => !isDefined(group.deletedAt) && group.viewId === viewId);
|
||||
.filter((group) => group.isActive && group.viewId === viewId);
|
||||
|
||||
if (groupsForView.length === 0) {
|
||||
return null;
|
||||
@@ -87,7 +87,7 @@ const computeNextPosition = ({
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(viewField: FlatViewField) =>
|
||||
!isDefined(viewField.deletedAt) && viewField.viewId === viewId,
|
||||
viewField.isActive && viewField.viewId === viewId,
|
||||
)
|
||||
.filter(
|
||||
(viewField: FlatViewField) =>
|
||||
@@ -199,6 +199,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position,
|
||||
aggregateOperation: null,
|
||||
isActive: true,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+1
@@ -66,6 +66,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
|
||||
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: createViewFieldInput.position ?? 0,
|
||||
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
|
||||
isActive: true,
|
||||
universalOverrides: null,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
|
||||
+2
@@ -65,6 +65,7 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
|
||||
pageLayoutUniversalIdentifier,
|
||||
widgetIds: [widgetId],
|
||||
widgetUniversalIdentifiers: [widgetUniversalIdentifier],
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
@@ -117,6 +118,7 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
|
||||
universalConfiguration,
|
||||
objectMetadataId: objectMetadata.id,
|
||||
objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ export const computeFlatViewFieldsToCreate = ({
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: index,
|
||||
aggregateOperation: null,
|
||||
isActive: true,
|
||||
universalOverrides: null,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
}));
|
||||
|
||||
+68
-56
@@ -10,6 +10,7 @@ import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { splitEntitiesByRemovalStrategy } from 'src/engine/metadata-modules/flat-entity/utils/split-entities-by-removal-strategy.util';
|
||||
import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
|
||||
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
@@ -167,9 +168,11 @@ export class PageLayoutUpdateService {
|
||||
flatViewMaps,
|
||||
});
|
||||
|
||||
const orphanedViewIds = this.collectOrphanedViewIdsFromDeletedWidgets({
|
||||
const orphanedViewIds = this.collectOrphanedViewIdsFromRemovedWidgets({
|
||||
widgetsToUpdate,
|
||||
widgetsToDelete,
|
||||
tabsToUpdate,
|
||||
tabsToDelete,
|
||||
flatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
@@ -278,7 +281,7 @@ export class PageLayoutUpdateService {
|
||||
toCreate: entitiesToCreate,
|
||||
toUpdate: entitiesToUpdate,
|
||||
toRestoreAndUpdate: entitiesToRestoreAndUpdate,
|
||||
idsToDelete,
|
||||
idsToRemove,
|
||||
} = computeDiffBetweenObjects<
|
||||
FlatPageLayoutTab,
|
||||
UpdatePageLayoutTabWithWidgetsInput
|
||||
@@ -286,6 +289,7 @@ export class PageLayoutUpdateService {
|
||||
existingObjects: existingTabs,
|
||||
receivedObjects: tabs,
|
||||
propertiesToCompare: FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES,
|
||||
isEntityIncluded: (entity) => entity.isActive,
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
@@ -313,6 +317,7 @@ export class PageLayoutUpdateService {
|
||||
icon: null,
|
||||
layoutMode: tabInput.layoutMode ?? PageLayoutTabLayoutMode.GRID,
|
||||
overrides: null,
|
||||
isActive: true,
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -346,38 +351,34 @@ export class PageLayoutUpdateService {
|
||||
title: tabInput.title,
|
||||
position: tabInput.position,
|
||||
layoutMode: tabInput.layoutMode ?? existingTab.layoutMode,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
updatedAt: now.toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
const tabsToDelete: FlatPageLayoutTab[] = idsToDelete
|
||||
.map((tabId) => {
|
||||
const existingTab = findFlatEntityByIdInFlatEntityMaps({
|
||||
const tabsToRemove = idsToRemove
|
||||
.map((tabId) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: tabId,
|
||||
flatEntityMaps: flatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingTab)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...existingTab,
|
||||
deletedAt: now.toISOString(),
|
||||
updatedAt: now.toISOString(),
|
||||
};
|
||||
})
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const { toHardDelete, toDeactivate } = splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: tabsToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
now: now.toISOString(),
|
||||
});
|
||||
|
||||
return {
|
||||
tabsToCreate,
|
||||
tabsToUpdate: [
|
||||
...tabsToUpdate,
|
||||
...tabsToRestoreAndUpdate,
|
||||
...tabsToDelete,
|
||||
...toDeactivate,
|
||||
],
|
||||
tabsToDelete: [],
|
||||
tabsToDelete: toHardDelete,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -414,9 +415,10 @@ export class PageLayoutUpdateService {
|
||||
} {
|
||||
const allWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const allWidgetsToUpdate: FlatPageLayoutWidget[] = [];
|
||||
const allWidgetsToDelete: FlatPageLayoutWidget[] = [];
|
||||
|
||||
for (const tabInput of tabs) {
|
||||
const { widgetsToCreate, widgetsToUpdate } =
|
||||
const { widgetsToCreate, widgetsToUpdate, widgetsToDelete } =
|
||||
this.computeWidgetOperationsForTab({
|
||||
tabId: tabInput.id,
|
||||
widgets: tabInput.widgets,
|
||||
@@ -434,12 +436,13 @@ export class PageLayoutUpdateService {
|
||||
|
||||
allWidgetsToCreate.push(...widgetsToCreate);
|
||||
allWidgetsToUpdate.push(...widgetsToUpdate);
|
||||
allWidgetsToDelete.push(...widgetsToDelete);
|
||||
}
|
||||
|
||||
return {
|
||||
widgetsToCreate: allWidgetsToCreate,
|
||||
widgetsToUpdate: allWidgetsToUpdate,
|
||||
widgetsToDelete: [],
|
||||
widgetsToDelete: allWidgetsToDelete,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -474,6 +477,7 @@ export class PageLayoutUpdateService {
|
||||
>): {
|
||||
widgetsToCreate: FlatPageLayoutWidget[];
|
||||
widgetsToUpdate: FlatPageLayoutWidget[];
|
||||
widgetsToDelete: FlatPageLayoutWidget[];
|
||||
} {
|
||||
for (const widgetInput of widgets) {
|
||||
this.validateChartFieldReferences({
|
||||
@@ -493,7 +497,7 @@ export class PageLayoutUpdateService {
|
||||
toCreate: entitiesToCreate,
|
||||
toUpdate: entitiesToUpdate,
|
||||
toRestoreAndUpdate: entitiesToRestoreAndUpdate,
|
||||
idsToDelete,
|
||||
idsToRemove,
|
||||
} = computeDiffBetweenObjects<
|
||||
FlatPageLayoutWidget,
|
||||
UpdatePageLayoutWidgetWithIdInput
|
||||
@@ -501,6 +505,7 @@ export class PageLayoutUpdateService {
|
||||
existingObjects: existingWidgets,
|
||||
receivedObjects: widgets,
|
||||
propertiesToCompare: FLAT_PAGE_LAYOUT_WIDGET_EDITABLE_PROPERTIES,
|
||||
isEntityIncluded: (entity) => entity.isActive,
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
@@ -527,6 +532,7 @@ export class PageLayoutUpdateService {
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
conditionalDisplay: null,
|
||||
overrides: null,
|
||||
isActive: true,
|
||||
universalConfiguration:
|
||||
fromPageLayoutWidgetConfigurationToUniversalConfiguration({
|
||||
configuration: widgetInput.configuration,
|
||||
@@ -595,7 +601,7 @@ export class PageLayoutUpdateService {
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
configuration: restoredConfiguration,
|
||||
deletedAt: null,
|
||||
isActive: true,
|
||||
updatedAt: now.toISOString(),
|
||||
...(isDefined(restoredConfiguration) && {
|
||||
universalConfiguration:
|
||||
@@ -614,32 +620,29 @@ export class PageLayoutUpdateService {
|
||||
};
|
||||
});
|
||||
|
||||
const widgetsToDelete: FlatPageLayoutWidget[] = idsToDelete
|
||||
.map((widgetId) => {
|
||||
const existingWidget = findFlatEntityByIdInFlatEntityMaps({
|
||||
const widgetsToRemove = idsToRemove
|
||||
.map((widgetId) =>
|
||||
findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: widgetId,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(existingWidget)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...existingWidget,
|
||||
deletedAt: now.toISOString(),
|
||||
updatedAt: now.toISOString(),
|
||||
};
|
||||
})
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const { toHardDelete, toDeactivate } = splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: widgetsToRemove,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
now: now.toISOString(),
|
||||
});
|
||||
|
||||
return {
|
||||
widgetsToCreate,
|
||||
widgetsToUpdate: [
|
||||
...widgetsToUpdate,
|
||||
...widgetsToRestoreAndUpdate,
|
||||
...widgetsToDelete,
|
||||
...toDeactivate,
|
||||
],
|
||||
widgetsToDelete: toHardDelete,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -665,24 +668,37 @@ export class PageLayoutUpdateService {
|
||||
});
|
||||
}
|
||||
|
||||
private collectOrphanedViewIdsFromDeletedWidgets({
|
||||
private collectOrphanedViewIdsFromRemovedWidgets({
|
||||
widgetsToUpdate,
|
||||
widgetsToDelete,
|
||||
tabsToUpdate,
|
||||
tabsToDelete,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: {
|
||||
widgetsToUpdate: FlatPageLayoutWidget[];
|
||||
widgetsToDelete: FlatPageLayoutWidget[];
|
||||
tabsToUpdate: FlatPageLayoutTab[];
|
||||
tabsToDelete: FlatPageLayoutTab[];
|
||||
flatPageLayoutWidgetMaps: Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatPageLayoutWidgetMaps'
|
||||
>['flatPageLayoutWidgetMaps'];
|
||||
}): string[] {
|
||||
const viewIdsToDelete = new Set<string>();
|
||||
const directlyDeletedWidgetIds = new Set<string>();
|
||||
const directlyRemovedWidgetIds = new Set<string>();
|
||||
|
||||
for (const widget of widgetsToDelete) {
|
||||
directlyRemovedWidgetIds.add(widget.id);
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
viewIdsToDelete.add(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const widget of widgetsToUpdate) {
|
||||
if (isDefined(widget.deletedAt)) {
|
||||
directlyDeletedWidgetIds.add(widget.id);
|
||||
if (!widget.isActive) {
|
||||
directlyRemovedWidgetIds.add(widget.id);
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
@@ -691,21 +707,17 @@ export class PageLayoutUpdateService {
|
||||
}
|
||||
}
|
||||
|
||||
const deletedTabIds = new Set(
|
||||
tabsToUpdate
|
||||
.filter((tab) => isDefined(tab.deletedAt))
|
||||
.map((tab) => tab.id),
|
||||
);
|
||||
const removedTabIds = new Set([
|
||||
...tabsToUpdate.filter((tab) => !tab.isActive).map((tab) => tab.id),
|
||||
...tabsToDelete.map((tab) => tab.id),
|
||||
]);
|
||||
|
||||
const allExistingWidgets = Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
for (const widget of allExistingWidgets) {
|
||||
if (
|
||||
!isDefined(widget.deletedAt) &&
|
||||
deletedTabIds.has(widget.pageLayoutTabId)
|
||||
) {
|
||||
if (widget.isActive && removedTabIds.has(widget.pageLayoutTabId)) {
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
if (isDefined(viewId)) {
|
||||
@@ -716,9 +728,9 @@ export class PageLayoutUpdateService {
|
||||
|
||||
for (const widget of allExistingWidgets) {
|
||||
if (
|
||||
!isDefined(widget.deletedAt) &&
|
||||
!directlyDeletedWidgetIds.has(widget.id) &&
|
||||
!deletedTabIds.has(widget.pageLayoutTabId)
|
||||
widget.isActive &&
|
||||
!directlyRemovedWidgetIds.has(widget.id) &&
|
||||
!removedTabIds.has(widget.pageLayoutTabId)
|
||||
) {
|
||||
const viewId = this.getViewIdFromFieldsWidget(widget);
|
||||
|
||||
|
||||
+36
-144
@@ -14,6 +14,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { splitEntitiesByRemovalStrategy } from 'src/engine/metadata-modules/flat-entity/utils/split-entities-by-removal-strategy.util';
|
||||
import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
@@ -141,17 +142,13 @@ export class FieldsWidgetUpsertService {
|
||||
flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(group) => !isDefined(group.deletedAt) && group.viewId === viewId,
|
||||
);
|
||||
.filter((group) => group.isActive && group.viewId === viewId);
|
||||
|
||||
const existingViewFields = Object.values(
|
||||
flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(field) => !isDefined(field.deletedAt) && field.viewId === viewId,
|
||||
);
|
||||
.filter((field) => field.isActive && field.viewId === viewId);
|
||||
|
||||
if (hasGroups) {
|
||||
await this.upsertFieldsWidgetWithGroups({
|
||||
@@ -228,7 +225,7 @@ export class FieldsWidgetUpsertService {
|
||||
|
||||
const groupsToCreate: FlatViewFieldGroup[] = [];
|
||||
const groupsToUpdate: FlatViewFieldGroup[] = [];
|
||||
const groupsToDelete: FlatViewFieldGroup[] = [];
|
||||
const groupsToDeactivate: FlatViewFieldGroup[] = [];
|
||||
|
||||
for (const inputGroup of inputGroups) {
|
||||
const existingGroup = existingGroups.find((g) => g.id === inputGroup.id);
|
||||
@@ -277,7 +274,7 @@ export class FieldsWidgetUpsertService {
|
||||
|
||||
for (const existingGroup of existingGroups) {
|
||||
if (!inputGroupIds.has(existingGroup.id)) {
|
||||
groupsToDelete.push(existingGroup);
|
||||
groupsToDeactivate.push(existingGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,6 +462,7 @@ export class FieldsWidgetUpsertService {
|
||||
aggregateOperation: null,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
@@ -472,15 +470,15 @@ export class FieldsWidgetUpsertService {
|
||||
}
|
||||
}
|
||||
|
||||
const fieldsWithStaleGroupOverrides =
|
||||
this.buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds: new Set(
|
||||
viewFieldsToUpdate.map((field) => field.id),
|
||||
),
|
||||
now,
|
||||
});
|
||||
const {
|
||||
toHardDelete: customGroupsToDelete,
|
||||
toDeactivate: deactivatedGroupUpdates,
|
||||
} = splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: groupsToDeactivate,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
@@ -488,16 +486,16 @@ export class FieldsWidgetUpsertService {
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: groupsToCreate,
|
||||
flatEntityToDelete: groupsToDelete,
|
||||
flatEntityToUpdate: groupsToUpdate,
|
||||
flatEntityToDelete: customGroupsToDelete,
|
||||
flatEntityToUpdate: [
|
||||
...groupsToUpdate,
|
||||
...deactivatedGroupUpdates,
|
||||
],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [
|
||||
...viewFieldsToUpdate,
|
||||
...fieldsWithStaleGroupOverrides,
|
||||
],
|
||||
flatEntityToUpdate: viewFieldsToUpdate,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
@@ -539,7 +537,7 @@ export class FieldsWidgetUpsertService {
|
||||
}): Promise<void> {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const groupsToDelete: FlatViewFieldGroup[] = [...existingGroups];
|
||||
const groupsToDeactivate: FlatViewFieldGroup[] = [...existingGroups];
|
||||
|
||||
const viewFieldsToUpdate = existingViewFields.flatMap((existingField) => {
|
||||
const inputField = inputFields.find(
|
||||
@@ -670,21 +668,22 @@ export class FieldsWidgetUpsertService {
|
||||
aggregateOperation: null,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
};
|
||||
});
|
||||
|
||||
const fieldsWithStaleGroupOverrides =
|
||||
this.buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds: new Set(
|
||||
viewFieldsToUpdate.map((field) => field.id),
|
||||
),
|
||||
now: new Date().toISOString(),
|
||||
});
|
||||
const {
|
||||
toHardDelete: customGroupsToDelete,
|
||||
toDeactivate: deactivatedGroupUpdates,
|
||||
} = splitEntitiesByRemovalStrategy({
|
||||
entitiesToRemove: groupsToDeactivate,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
@@ -692,16 +691,13 @@ export class FieldsWidgetUpsertService {
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: groupsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
flatEntityToDelete: customGroupsToDelete,
|
||||
flatEntityToUpdate: deactivatedGroupUpdates,
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [
|
||||
...viewFieldsToUpdate,
|
||||
...fieldsWithStaleGroupOverrides,
|
||||
],
|
||||
flatEntityToUpdate: viewFieldsToUpdate,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
@@ -718,111 +714,6 @@ export class FieldsWidgetUpsertService {
|
||||
}
|
||||
}
|
||||
|
||||
private buildFieldUpdatesForStaleGroupOverrides({
|
||||
existingViewFields,
|
||||
groupsToDelete,
|
||||
alreadyUpdatedFieldIds,
|
||||
now,
|
||||
}: {
|
||||
existingViewFields: FlatViewField[];
|
||||
groupsToDelete: FlatViewFieldGroup[];
|
||||
alreadyUpdatedFieldIds: Set<string>;
|
||||
now: string;
|
||||
}): FlatViewField[] {
|
||||
if (groupsToDelete.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const deletedGroupIds = new Set(groupsToDelete.map((group) => group.id));
|
||||
|
||||
return existingViewFields
|
||||
.filter((field) => {
|
||||
if (alreadyUpdatedFieldIds.has(field.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const overriddenGroupId = field.overrides?.viewFieldGroupId;
|
||||
|
||||
const hasStaleOverride =
|
||||
isDefined(overriddenGroupId) &&
|
||||
typeof overriddenGroupId === 'string' &&
|
||||
deletedGroupIds.has(overriddenGroupId);
|
||||
|
||||
const hasStaleBase =
|
||||
overriddenGroupId === undefined &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
const hasStaleBaseHiddenByNullOverride =
|
||||
overriddenGroupId === null &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
return (
|
||||
hasStaleOverride || hasStaleBase || hasStaleBaseHiddenByNullOverride
|
||||
);
|
||||
})
|
||||
.map((field) => {
|
||||
const overriddenGroupId = field.overrides?.viewFieldGroupId;
|
||||
const hasStaleOverride =
|
||||
isDefined(overriddenGroupId) &&
|
||||
typeof overriddenGroupId === 'string' &&
|
||||
deletedGroupIds.has(overriddenGroupId);
|
||||
|
||||
if (hasStaleOverride) {
|
||||
const { viewFieldGroupId: _, ...remainingOverrides } =
|
||||
field.overrides!;
|
||||
|
||||
const cleanedOverrides =
|
||||
Object.keys(remainingOverrides).length > 0
|
||||
? (remainingOverrides as typeof field.overrides)
|
||||
: null;
|
||||
|
||||
const baseGroupIsAlsoStale =
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId);
|
||||
|
||||
return {
|
||||
...field,
|
||||
...(baseGroupIsAlsoStale
|
||||
? {
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
}
|
||||
: {}),
|
||||
overrides: cleanedOverrides,
|
||||
universalOverrides: isDefined(cleanedOverrides)
|
||||
? fromViewFieldOverridesToUniversalOverrides({
|
||||
overrides: cleanedOverrides,
|
||||
viewFieldGroupUniversalIdentifierById: {},
|
||||
})
|
||||
: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
overriddenGroupId === null &&
|
||||
isDefined(field.viewFieldGroupId) &&
|
||||
deletedGroupIds.has(field.viewFieldGroupId)
|
||||
) {
|
||||
return {
|
||||
...field,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...field,
|
||||
viewFieldGroupId: null,
|
||||
viewFieldGroupUniversalIdentifier: null,
|
||||
updatedAt: now,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private buildGroupToCreate({
|
||||
inputGroup,
|
||||
viewId,
|
||||
@@ -858,6 +749,7 @@ export class FieldsWidgetUpsertService {
|
||||
isVisible: inputGroup.isVisible,
|
||||
viewId,
|
||||
viewUniversalIdentifier,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+1
@@ -347,6 +347,7 @@ export class ViewFieldGroupService {
|
||||
where: {
|
||||
workspaceId,
|
||||
viewId,
|
||||
isActive: true,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
|
||||
+2
@@ -349,6 +349,7 @@ export class ViewFieldService {
|
||||
return this.viewFieldRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
isActive: true,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
@@ -364,6 +365,7 @@ export class ViewFieldService {
|
||||
where: {
|
||||
workspaceId,
|
||||
viewId,
|
||||
isActive: true,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { position: 'ASC' },
|
||||
|
||||
+4
@@ -62,6 +62,7 @@ export const computeFieldsWidgetViewFieldsAndGroupsToCreate = ({
|
||||
name: 'General',
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
createdAt,
|
||||
@@ -83,6 +84,7 @@ export const computeFieldsWidgetViewFieldsAndGroupsToCreate = ({
|
||||
deletedAt: null,
|
||||
universalIdentifier: v4(),
|
||||
isVisible,
|
||||
isActive: true,
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: index,
|
||||
aggregateOperation: null,
|
||||
@@ -101,6 +103,7 @@ export const computeFieldsWidgetViewFieldsAndGroupsToCreate = ({
|
||||
name: 'Other',
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
createdAt,
|
||||
@@ -122,6 +125,7 @@ export const computeFieldsWidgetViewFieldsAndGroupsToCreate = ({
|
||||
deletedAt: null,
|
||||
universalIdentifier: v4(),
|
||||
isVisible,
|
||||
isActive: true,
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
position: index,
|
||||
aggregateOperation: null,
|
||||
|
||||
+1
@@ -68,6 +68,7 @@ export const createStandardPageLayoutTabFlatMetadata = ({
|
||||
pageLayoutUniversalIdentifier: layout.universalIdentifier,
|
||||
widgetIds,
|
||||
widgetUniversalIdentifiers,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+1
@@ -104,6 +104,7 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
|
||||
universalConfiguration,
|
||||
objectMetadataId,
|
||||
objectMetadataUniversalIdentifier,
|
||||
isActive: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ export const createStandardViewFieldGroupFlatMetadata = <
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
universalIdentifier: viewFieldGroupDefinition.universalIdentifier,
|
||||
applicationId: twentyStandardApplicationId,
|
||||
isActive: true,
|
||||
applicationUniversalIdentifier:
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
workspaceId,
|
||||
|
||||
+1
@@ -120,6 +120,7 @@ export const createStandardViewFieldFlatMetadata = <
|
||||
isVisible,
|
||||
size,
|
||||
aggregateOperation,
|
||||
isActive: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
|
||||
@@ -8,4 +8,7 @@ export abstract class OverridableEntity<
|
||||
> extends SyncableEntity {
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
overrides: JsonbProperty<TOverrides> | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
+30
-30
@@ -80,6 +80,36 @@ exports[`Page layout widget restore via bulk update should succeed should handle
|
||||
"title": "Test Tab For Widget Restore",
|
||||
"updatedAt": Any<String>,
|
||||
"widgets": [
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": null,
|
||||
"label": null,
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 1,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": Any<String>,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Restored Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
{
|
||||
"configuration": {
|
||||
"configurationType": "IFRAME",
|
||||
@@ -120,36 +150,6 @@ exports[`Page layout widget restore via bulk update should succeed should handle
|
||||
"type": "IFRAME",
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
{
|
||||
"configuration": {
|
||||
"aggregateFieldMetadataId": Any<String>,
|
||||
"aggregateOperation": "SUM",
|
||||
"configurationType": "AGGREGATE_CHART",
|
||||
"description": null,
|
||||
"displayDataLabel": true,
|
||||
"filter": null,
|
||||
"firstDayOfTheWeek": 1,
|
||||
"format": null,
|
||||
"label": null,
|
||||
"prefix": null,
|
||||
"suffix": null,
|
||||
"timezone": "UTC",
|
||||
},
|
||||
"createdAt": Any<String>,
|
||||
"deletedAt": null,
|
||||
"gridPosition": {
|
||||
"column": 0,
|
||||
"columnSpan": 1,
|
||||
"row": 1,
|
||||
"rowSpan": 1,
|
||||
},
|
||||
"id": Any<String>,
|
||||
"objectMetadataId": Any<String>,
|
||||
"pageLayoutTabId": Any<String>,
|
||||
"title": "Restored Widget",
|
||||
"type": "GRAPH",
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
-9
@@ -1,5 +1,4 @@
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { fetchTestFieldMetadataIds } from 'test/integration/metadata/suites/page-layout-widget/utils/fetch-test-field-metadata-ids.util';
|
||||
import { createOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/create-one-page-layout.util';
|
||||
import { destroyOnePageLayout } from 'test/integration/metadata/suites/page-layout/utils/destroy-one-page-layout.util';
|
||||
@@ -193,14 +192,6 @@ describe('Page layout with tabs update should succeed', () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: testTabId1 },
|
||||
});
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: testTabId2 },
|
||||
});
|
||||
await destroyOnePageLayout({
|
||||
expectToFail: false,
|
||||
input: { id: testPageLayoutId },
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { isDefined } from '@/utils/validation';
|
||||
|
||||
import { computeDiffBetweenObjects } from '../compute-diff-between-objects';
|
||||
|
||||
const isEntityIncludedByDeletedAt = (entity: {
|
||||
deletedAt: string | null;
|
||||
}): boolean => !isDefined(entity.deletedAt);
|
||||
|
||||
const isEntityIncludedByIsActive = (entity: { isActive: boolean }): boolean =>
|
||||
entity.isActive;
|
||||
|
||||
describe('computeDiffBetweenObjects', () => {
|
||||
it('should return the correct diff', () => {
|
||||
const existingObjects = [
|
||||
@@ -15,13 +24,14 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [{ id: '3', name: 'Object 3' }],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: ['2'],
|
||||
idsToRemove: ['2'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,13 +43,14 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: [],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: [],
|
||||
idsToRemove: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,13 +62,14 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [{ id: '1', name: 'Object 1' }],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: [],
|
||||
idsToRemove: [],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -69,13 +81,14 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: ['1'],
|
||||
idsToRemove: ['1'],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -87,17 +100,18 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [{ id: '1', name: 'Updated Object 1' }],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: [],
|
||||
idsToRemove: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should restore and update deleted objects', () => {
|
||||
it('should restore and update excluded objects when using deletedAt', () => {
|
||||
const existingObjects = [
|
||||
{ id: '1', name: 'Object 1', deletedAt: '2024-01-01' },
|
||||
];
|
||||
@@ -107,17 +121,18 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [{ id: '1', name: 'Restored Object 1' }],
|
||||
idsToDelete: [],
|
||||
idsToRemove: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include deleted objects in idsToDelete', () => {
|
||||
it('should not include excluded objects in idsToRemove', () => {
|
||||
const existingObjects = [
|
||||
{ id: '1', name: 'Object 1', deletedAt: null },
|
||||
{ id: '2', name: 'Object 2', deletedAt: '2024-01-01' },
|
||||
@@ -128,13 +143,55 @@ describe('computeDiffBetweenObjects', () => {
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByDeletedAt,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToDelete: ['1'],
|
||||
idsToRemove: ['1'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should restore and update inactive objects when using isActive', () => {
|
||||
const existingObjects = [{ id: '1', name: 'Object 1', isActive: false }];
|
||||
const receivedObjects = [{ id: '1', name: 'Restored Object 1' }];
|
||||
|
||||
const diff = computeDiffBetweenObjects({
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByIsActive,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [{ id: '1', name: 'Restored Object 1' }],
|
||||
idsToRemove: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include inactive objects in idsToRemove when using isActive', () => {
|
||||
const existingObjects = [
|
||||
{ id: '1', name: 'Object 1', isActive: true },
|
||||
{ id: '2', name: 'Object 2', isActive: false },
|
||||
];
|
||||
const receivedObjects: { id: string; name: string }[] = [];
|
||||
|
||||
const diff = computeDiffBetweenObjects({
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare: ['name'],
|
||||
isEntityIncluded: isEntityIncludedByIsActive,
|
||||
});
|
||||
|
||||
expect(diff).toEqual({
|
||||
toCreate: [],
|
||||
toUpdate: [],
|
||||
toRestoreAndUpdate: [],
|
||||
idsToRemove: ['1'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ type Diff<T extends { id: string }> = {
|
||||
toCreate: T[];
|
||||
toUpdate: T[];
|
||||
toRestoreAndUpdate: T[];
|
||||
idsToDelete: string[];
|
||||
idsToRemove: string[];
|
||||
};
|
||||
|
||||
const extractProperties = <T extends { id: string }>(
|
||||
@@ -27,15 +27,17 @@ type ComputeDiffBetweenObjectsParams<
|
||||
existingObjects: T[];
|
||||
receivedObjects: K[];
|
||||
propertiesToCompare: (keyof K & keyof T)[];
|
||||
isEntityIncluded: (entity: NoInfer<T>) => boolean;
|
||||
};
|
||||
|
||||
export const computeDiffBetweenObjects = <
|
||||
T extends { id: string; deletedAt: string | null },
|
||||
T extends { id: string },
|
||||
K extends { id: string },
|
||||
>({
|
||||
existingObjects,
|
||||
receivedObjects,
|
||||
propertiesToCompare,
|
||||
isEntityIncluded,
|
||||
}: ComputeDiffBetweenObjectsParams<T, K>): Diff<K> => {
|
||||
const toCreate: K[] = [];
|
||||
const toUpdate: K[] = [];
|
||||
@@ -52,7 +54,7 @@ export const computeDiffBetweenObjects = <
|
||||
const existingEntity = existingEntitiesMap.get(receivedObject.id);
|
||||
|
||||
if (isDefined(existingEntity)) {
|
||||
if (isDefined(existingEntity.deletedAt)) {
|
||||
if (!isEntityIncluded(existingEntity)) {
|
||||
toRestoreAndUpdate.push(receivedObject);
|
||||
} else {
|
||||
const comparableExistingEntity = extractProperties(
|
||||
@@ -74,8 +76,8 @@ export const computeDiffBetweenObjects = <
|
||||
}
|
||||
}
|
||||
|
||||
const idsToDelete = existingObjects
|
||||
.filter((existingEntity) => !isDefined(existingEntity.deletedAt))
|
||||
const idsToRemove = existingObjects
|
||||
.filter((existingEntity) => isEntityIncluded(existingEntity))
|
||||
.filter((existingEntity) => !receivedEntitiesMap.has(existingEntity.id))
|
||||
.map((entity) => entity.id);
|
||||
|
||||
@@ -83,6 +85,6 @@ export const computeDiffBetweenObjects = <
|
||||
toCreate,
|
||||
toUpdate,
|
||||
toRestoreAndUpdate,
|
||||
idsToDelete,
|
||||
idsToRemove,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user