feat(server): add isSystemSideEffect & merge createOneObject/createOneField side-effect migrations (#21673)
## Context When an object is created via the metadata API, `createOneObject` creates its side-effect entities (INDEX view + viewFields, indexes, navigation menu item, "go to" command menu item, record-page fields view, page layout/tabs/widgets) across **three separate `validateBuildAndRunWorkspaceMigration` calls**, purely because the protection behavior (mutations → overrides, delete → deactivate, reset → reactivate) was keyed on *"owned by the standard app"*, forcing the side effects into batches with different application owners. This misrepresents ownership and breaks atomicity. This PR separates two orthogonal concepts: - **Ownership** (`applicationId`), the true owner: the caller's application (the workspace custom app today, 3rd-party apps later). - **Protection** (`isSystemSideEffect`), the row was generated by the system, so user mutations route to overrides, deletion becomes deactivation, and reset restores defaults. Once side effects are re-owned to the caller, the old `applicationId === standardApp` check can no longer tell an original side-effect row from a user-added one so a dedicated `isSystemSideEffect` flag carries the protection instead. This is **PR 1 of 2** (forward-only). It makes newly created objects and fields correct; existing workspaces are handled by a follow-up backfill (see *Out of scope*). ## What this PR does - **`isSystemSideEffect` column** on the 8 affected entities (`view`, `viewField`, `indexMetadata`, `commandMenuItem`, `pageLayout`, `pageLayoutTab`, `pageLayoutWidget`, `fieldMetadata`), with `@WasIntroducedInUpgrade` + an entry in the flat-entity property configuration (`toCompare: true`, read-only). - **Single atomic migration in `createOneObject`**: the three `validateBuildAndRunWorkspaceMigration` calls are merged into one, owned by the caller (`resolvedOwnerFlatApplication`) and the record-page view/fields, page layout, and navigation command item are re-owned to the caller and flagged `isSystemSideEffect: true`. `buildNavigationFlatCommandMenuItem` is parameterized with `applicationUniversalIdentifier` (no longer hardcoded to the standard app). - **Field-creation side effects** (`createManyFields`/`createOneField` already run as a single caller-owned migration, so no re-ownership/merge was needed): the auto-created viewField is flagged `isSystemSideEffect: true`, and a new field now also propagates to the object's **INDEX/table view** (added there as a **hidden** column, `isVisible: false`) in addition to the record-page FIELDS widget. The INDEX view is targeted directly by `key = INDEX` (it is not a page-layout widget), de-duplicated per `(viewId, fieldMetadataUniversalIdentifier)` to respect the per-view unique index. The unique-field index is likewise flagged the inverse relation field stays unflagged (`isSystem: false`). - **Protection predicate** extended: `isCallerOverridingEntity` and the removal/reset split strategies now treat `isSystemSideEffect` rows as protected even when caller-owned (route to overrides / deactivate / reset) and the page-layout-reset guards allow resetting flagged entities. - **Standard compute maps** set the flag consistently so a re-sync produces no diff (standard-object side effects stay `false`; per-object nav command items and custom-object base fields are `true`). - **Read-only GraphQL exposure** of `isSystemSideEffect` on the view / view-field / page-layout / tab / widget / command-menu-item DTOs (not exposed on create/update inputs). => Todo: needs to take this new flag into account. This is fine for now because isSystem remains on object/field. - **Fast instance command** (`2-14`) adding the 8 columns (`NOT NULL DEFAULT false`). ## Scope decisions - **`pageLayout` is not an `OverridableEntity`**, its own row has nothing user-overridable (all customization lives on tabs/widgets). It's dual-purpose (`RECORD_PAGE` side-effect vs. user `DASHBOARD`), so it gets `isSystemSideEffect` for protection only, no `overrides` jsonb. - **`navigationMenuItem` is out of scope.**: Those are side effects only for the metadata API and not marked as "system" (they can be deleted/updated etc...) - **`viewFieldGroup` is not a side effect**, it's only created via the explicit view-field-group API, never by object/field creation, so it gets no flag. ## Out of scope (follow-ups) **PR 2** — slow per-workspace backfill (re-own + flag existing side effects, recreate missing ones) and deterministic v5 identifiers for base fields / pageLayout / tab. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21673?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+1
@@ -356,6 +356,7 @@ export class CommandMenuItemService {
|
||||
flatCommandMenuItemToDelete.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
isSystemSideEffect: flatCommandMenuItemToDelete.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const deactivatedFlatCommandMenuItem = {
|
||||
|
||||
+7
@@ -14,6 +14,7 @@ import { type SerializedRelation } from 'twenty-shared/types';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { type CommandMenuItemPayload } from 'src/engine/metadata-modules/command-menu-item/dtos/command-menu-item-payload.union';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
|
||||
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
|
||||
import { FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
|
||||
@@ -129,6 +130,12 @@ export class CommandMenuItemEntity
|
||||
@JoinColumn({ name: 'pageLayoutId' })
|
||||
pageLayout: Relation<PageLayoutEntity> | null;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ const baseCommandMenuItem = {
|
||||
payload: { objectMetadataItemId: 'obj-id-1' },
|
||||
workspaceId: 'ws-id-1',
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
+7
@@ -22,6 +22,7 @@ import {
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { WasRemovedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-removed-in-upgrade.decorator';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { RENAME_IS_UI_READ_ONLY_TO_IS_UI_EDITABLE_UPGRADE_COMMAND_NAME } from 'src/engine/metadata-modules/object-metadata/constants/rename-is-ui-read-only-to-is-ui-editable-upgrade-command-name.constant';
|
||||
import { type FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
|
||||
import { AssignIfIsGivenFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/assign-if-is-given-field-metadata-type.type';
|
||||
@@ -122,6 +123,12 @@ export class FieldMetadataEntity<
|
||||
@Column({ default: false })
|
||||
isSystem: boolean;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName:
|
||||
RENAME_IS_UI_READ_ONLY_TO_IS_UI_EDITABLE_UPGRADE_COMMAND_NAME,
|
||||
|
||||
+3
-5
@@ -10,7 +10,6 @@ import {
|
||||
NAVIGATION_INTERPOLATED_LABEL,
|
||||
NAVIGATION_INTERPOLATED_SHORT_LABEL,
|
||||
} from 'src/engine/metadata-modules/flat-command-menu-item/utils/build-navigation-flat-command-menu-item.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
const NAVIGATION_COMMAND_UUID_NAMESPACE =
|
||||
'b31830da-2ae0-48eb-a915-12fa4ab96dd3';
|
||||
@@ -26,6 +25,7 @@ const baseArgs = {
|
||||
objectMetadata: baseObjectMetadata,
|
||||
commandMenuItemId: 'cmd-id-1',
|
||||
applicationId: 'app-id-1',
|
||||
applicationUniversalIdentifier: 'app-universal-1',
|
||||
workspaceId: 'ws-id-1',
|
||||
position: 5,
|
||||
now: '2026-01-01T00:00:00.000Z',
|
||||
@@ -86,12 +86,10 @@ describe('buildNavigationFlatCommandMenuItem', () => {
|
||||
expect(result.position).toBe(5);
|
||||
});
|
||||
|
||||
it('should set applicationUniversalIdentifier from TWENTY_STANDARD_APPLICATION', () => {
|
||||
it('should set applicationUniversalIdentifier from the provided argument', () => {
|
||||
const result = buildNavigationFlatCommandMenuItem(baseArgs);
|
||||
|
||||
expect(result.applicationUniversalIdentifier).toBe(
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
);
|
||||
expect(result.applicationUniversalIdentifier).toBe('app-universal-1');
|
||||
});
|
||||
|
||||
it('should set engineComponentKey to NAVIGATION', () => {
|
||||
|
||||
+4
-3
@@ -6,7 +6,6 @@ import { v5 } from 'uuid';
|
||||
import { CommandMenuItemAvailabilityType } from 'src/engine/metadata-modules/command-menu-item/enums/command-menu-item-availability-type.enum';
|
||||
import { EngineComponentKey } from 'src/engine/metadata-modules/command-menu-item/enums/engine-component-key.enum';
|
||||
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
|
||||
export const NAVIGATION_COMMAND_UUID_NAMESPACE =
|
||||
'b31830da-2ae0-48eb-a915-12fa4ab96dd3';
|
||||
@@ -51,6 +50,7 @@ export const buildNavigationFlatCommandMenuItem = ({
|
||||
objectMetadata,
|
||||
commandMenuItemId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
workspaceId,
|
||||
position,
|
||||
now,
|
||||
@@ -63,6 +63,7 @@ export const buildNavigationFlatCommandMenuItem = ({
|
||||
};
|
||||
commandMenuItemId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
workspaceId: string;
|
||||
position: number;
|
||||
now: string;
|
||||
@@ -82,8 +83,7 @@ export const buildNavigationFlatCommandMenuItem = ({
|
||||
id: commandMenuItemId,
|
||||
universalIdentifier,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier:
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
workspaceId,
|
||||
label: NAVIGATION_INTERPOLATED_LABEL,
|
||||
shortLabel: NAVIGATION_INTERPOLATED_SHORT_LABEL,
|
||||
@@ -105,6 +105,7 @@ export const buildNavigationFlatCommandMenuItem = ({
|
||||
pageLayoutId: null,
|
||||
pageLayoutUniversalIdentifier: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
|
||||
+1
@@ -116,6 +116,7 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({
|
||||
pageLayoutId: commandMenuItemEntity.pageLayoutId,
|
||||
pageLayoutUniversalIdentifier,
|
||||
isActive: commandMenuItemEntity.isActive,
|
||||
isSystemSideEffect: commandMenuItemEntity.isSystemSideEffect,
|
||||
overrides: commandMenuItemEntity.overrides,
|
||||
universalOverrides,
|
||||
};
|
||||
|
||||
+1
@@ -77,6 +77,7 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
|
||||
+1
@@ -53,6 +53,7 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatCommandMenuItem.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingFlatCommandMenuItem.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+40
@@ -40,6 +40,11 @@ type MetadataEntityPropertyConfiguration<
|
||||
|
||||
export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
fieldMetadata: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
defaultValue: {
|
||||
toCompare: true,
|
||||
toStringify: true,
|
||||
@@ -265,6 +270,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
view: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
key: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
deletedAt: {
|
||||
toCompare: true,
|
||||
@@ -445,6 +455,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
viewField: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
isVisible: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
@@ -550,6 +565,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
index: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
indexType: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
@@ -893,6 +913,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
pageLayout: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
name: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
type: { toCompare: true, toStringify: false, universalProperty: undefined },
|
||||
objectMetadataId: {
|
||||
@@ -923,6 +948,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
title: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
@@ -997,6 +1027,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
pageLayoutTab: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
title: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
@@ -1092,6 +1127,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
},
|
||||
},
|
||||
commandMenuItem: {
|
||||
isSystemSideEffect: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
label: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
|
||||
+3
-1
@@ -1,5 +1,6 @@
|
||||
type EntityWithApplicationIdentifier = {
|
||||
applicationUniversalIdentifier: string;
|
||||
isSystemSideEffect?: boolean;
|
||||
};
|
||||
|
||||
export const splitEntitiesByRemovalStrategy = <
|
||||
@@ -22,7 +23,8 @@ export const splitEntitiesByRemovalStrategy = <
|
||||
for (const entity of entitiesToRemove) {
|
||||
if (
|
||||
entity.applicationUniversalIdentifier ===
|
||||
workspaceCustomApplicationUniversalIdentifier
|
||||
workspaceCustomApplicationUniversalIdentifier &&
|
||||
!entity.isSystemSideEffect
|
||||
) {
|
||||
toHardDelete.push(entity);
|
||||
} else {
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ type EntityWithApplicationIdentifierAndOverrides = {
|
||||
applicationUniversalIdentifier: string;
|
||||
isActive: boolean;
|
||||
overrides: unknown;
|
||||
isSystemSideEffect?: boolean;
|
||||
};
|
||||
|
||||
export const splitEntitiesByResetStrategy = <
|
||||
@@ -28,7 +29,8 @@ export const splitEntitiesByResetStrategy = <
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
entity.applicationUniversalIdentifier ===
|
||||
workspaceCustomApplicationUniversalIdentifier
|
||||
workspaceCustomApplicationUniversalIdentifier &&
|
||||
!entity.isSystemSideEffect
|
||||
) {
|
||||
toHardDelete.push(entity);
|
||||
} else {
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
|
||||
icon: 'icon',
|
||||
id: faker.string.uuid(),
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
name: 'flatFieldMetadataName',
|
||||
label: 'flat field metadata label',
|
||||
isNullable: true,
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ export const getRelationTargetFlatFieldMetadataMock = ({
|
||||
const createdAt = '2024-01-01T00:00:00.000Z';
|
||||
|
||||
return {
|
||||
isSystemSideEffect: false,
|
||||
calendarViewIds: [],
|
||||
mainGroupByFieldMetadataViewIds: [],
|
||||
viewFilterIds: [],
|
||||
|
||||
+6
@@ -127,6 +127,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"isLabelSyncedWithName": false,
|
||||
"isNullable": true,
|
||||
"isSystem": false,
|
||||
"isSystemSideEffect": false,
|
||||
"isUIEditable": true,
|
||||
"isUnique": false,
|
||||
"kanbanAggregateOperationViewUniversalIdentifiers": [],
|
||||
@@ -161,6 +162,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"isLabelSyncedWithName": false,
|
||||
"isNullable": true,
|
||||
"isSystem": false,
|
||||
"isSystemSideEffect": false,
|
||||
"isUIEditable": true,
|
||||
"isUnique": false,
|
||||
"kanbanAggregateOperationViewUniversalIdentifiers": [],
|
||||
@@ -197,6 +199,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"isLabelSyncedWithName": false,
|
||||
"isNullable": true,
|
||||
"isSystem": false,
|
||||
"isSystemSideEffect": false,
|
||||
"isUIEditable": true,
|
||||
"isUnique": false,
|
||||
"kanbanAggregateOperationViewUniversalIdentifiers": [],
|
||||
@@ -231,6 +234,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"isLabelSyncedWithName": false,
|
||||
"isNullable": true,
|
||||
"isSystem": false,
|
||||
"isSystemSideEffect": false,
|
||||
"isUIEditable": true,
|
||||
"isUnique": false,
|
||||
"kanbanAggregateOperationViewUniversalIdentifiers": [],
|
||||
@@ -263,6 +267,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"indexType": "BTREE",
|
||||
"indexWhereClause": null,
|
||||
"isCustom": true,
|
||||
"isSystemSideEffect": true,
|
||||
"isUnique": false,
|
||||
"name": "IDX_f687e4e4252800dddd8e5518362",
|
||||
"objectMetadataUniversalIdentifier": Any<String>,
|
||||
@@ -285,6 +290,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
|
||||
"indexType": "BTREE",
|
||||
"indexWhereClause": null,
|
||||
"isCustom": true,
|
||||
"isSystemSideEffect": true,
|
||||
"isUnique": false,
|
||||
"name": "IDX_cb15d901e889e25d0a9acecb595",
|
||||
"objectMetadataUniversalIdentifier": Any<String>,
|
||||
|
||||
+1
@@ -38,6 +38,7 @@ export const generateIndexForFlatFieldMetadata = ({
|
||||
indexWhereClause: null,
|
||||
isCustom: true,
|
||||
isUnique: flatFieldMetadata.isUnique ?? false,
|
||||
isSystemSideEffect: true,
|
||||
objectMetadataUniversalIdentifier:
|
||||
flatObjectMetadata.universalIdentifier,
|
||||
universalIdentifier: indexMetadataUniversalIdentifier,
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ export const getDefaultFlatFieldMetadata = ({
|
||||
createFieldInput.isRemoteCreation,
|
||||
),
|
||||
isSystem: createFieldInput.isSystem ?? false,
|
||||
isSystemSideEffect: false,
|
||||
isUnique: createFieldInput.isUnique ?? false,
|
||||
label: createFieldInput.label,
|
||||
name: createFieldInput.name,
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ export const getFlatIndexMetadataMock = (
|
||||
indexWhereClause: null,
|
||||
isCustom: false,
|
||||
isUnique: false,
|
||||
isSystemSideEffect: false,
|
||||
name: 'defaultFlatIndexMetadataName',
|
||||
updatedAt: createdAt,
|
||||
workspaceId: faker.string.uuid(),
|
||||
|
||||
+8
-2
@@ -71,6 +71,7 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
|
||||
position: lowestViewFieldPosition - 1,
|
||||
isVisible: true,
|
||||
isActive: true,
|
||||
isSystemSideEffect: flatView.isSystemSideEffect,
|
||||
size: DEFAULT_VIEW_FIELD_SIZE,
|
||||
viewId: flatView.id,
|
||||
viewUniversalIdentifier: flatView.universalIdentifier,
|
||||
@@ -92,11 +93,16 @@ export const recomputeViewFieldIdentifierAfterFlatObjectIdentifierUpdate = ({
|
||||
|
||||
accumulator.flatViewFieldsToCreate.push(flatViewFieldToCreate);
|
||||
} else if (
|
||||
labelMetadataIdentifierViewField.position > lowestViewFieldPosition
|
||||
labelMetadataIdentifierViewField.position > lowestViewFieldPosition ||
|
||||
labelMetadataIdentifierViewField.isVisible === false
|
||||
) {
|
||||
const updatedFlatViewField = {
|
||||
...labelMetadataIdentifierViewField,
|
||||
position: lowestViewFieldPosition - 1,
|
||||
position:
|
||||
labelMetadataIdentifierViewField.position > lowestViewFieldPosition
|
||||
? lowestViewFieldPosition - 1
|
||||
: labelMetadataIdentifierViewField.position,
|
||||
isVisible: true,
|
||||
};
|
||||
|
||||
accumulator.flatViewFieldsToUpdate.push(updatedFlatViewField);
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({
|
||||
title: createPageLayoutTabInput.title,
|
||||
position: createPageLayoutTabInput.position ?? 0,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
pageLayoutId: createPageLayoutTabInput.pageLayoutId,
|
||||
pageLayoutUniversalIdentifier,
|
||||
workspaceId,
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ export const fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow = ({
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatPageLayoutTabToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingFlatPageLayoutTabToUpdate.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ export const transformPageLayoutTabEntityToFlatPageLayoutTab = ({
|
||||
title: pageLayoutTabEntity.title,
|
||||
position: pageLayoutTabEntity.position,
|
||||
isActive: pageLayoutTabEntity.isActive,
|
||||
isSystemSideEffect: pageLayoutTabEntity.isSystemSideEffect,
|
||||
pageLayoutId: pageLayoutTabEntity.pageLayoutId,
|
||||
workspaceId: pageLayoutTabEntity.workspaceId,
|
||||
universalIdentifier: pageLayoutTabEntity.universalIdentifier,
|
||||
|
||||
+1
@@ -65,6 +65,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
|
||||
id: pageLayoutWidgetId,
|
||||
...commonProperties,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
workspaceId,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
|
||||
+2
@@ -89,6 +89,8 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatPageLayoutWidgetToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect:
|
||||
existingFlatPageLayoutWidgetToUpdate.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export const fromCreatePageLayoutInputToFlatPageLayoutToCreate = ({
|
||||
type: createPageLayoutInput.type ?? PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: createPageLayoutInput.objectMetadataId ?? null,
|
||||
objectMetadataUniversalIdentifier,
|
||||
isSystemSideEffect: false,
|
||||
workspaceId,
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ export const transformPageLayoutEntityToFlatPageLayout = ({
|
||||
name: pageLayoutEntity.name,
|
||||
type: pageLayoutEntity.type,
|
||||
objectMetadataId: pageLayoutEntity.objectMetadataId,
|
||||
isSystemSideEffect: pageLayoutEntity.isSystemSideEffect,
|
||||
workspaceId: pageLayoutEntity.workspaceId,
|
||||
universalIdentifier: pageLayoutEntity.universalIdentifier,
|
||||
applicationId: pageLayoutEntity.applicationId,
|
||||
|
||||
+1
@@ -59,6 +59,7 @@ export const fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow =
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewFieldGroupToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: false,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+211
-2
@@ -1,3 +1,5 @@
|
||||
import { ViewKey } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
|
||||
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
|
||||
@@ -21,13 +23,30 @@ const buildEmptyFlatEntityMaps = () => ({
|
||||
});
|
||||
|
||||
const buildFlatViewMaps = (
|
||||
entries: { id: string; universalIdentifier: string }[] = [],
|
||||
entries: {
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
key?: ViewKey | null;
|
||||
isActive?: boolean;
|
||||
isSystemSideEffect?: boolean;
|
||||
objectMetadataUniversalIdentifier?: string | null;
|
||||
deletedAt?: string | null;
|
||||
}[] = [],
|
||||
): FlatViewMaps =>
|
||||
({
|
||||
byUniversalIdentifier: Object.fromEntries(
|
||||
entries.map((entry) => [
|
||||
entry.universalIdentifier,
|
||||
{ universalIdentifier: entry.universalIdentifier, id: entry.id },
|
||||
{
|
||||
universalIdentifier: entry.universalIdentifier,
|
||||
id: entry.id,
|
||||
key: entry.key ?? null,
|
||||
isActive: entry.isActive ?? true,
|
||||
isSystemSideEffect: entry.isSystemSideEffect ?? false,
|
||||
objectMetadataUniversalIdentifier:
|
||||
entry.objectMetadataUniversalIdentifier ?? null,
|
||||
deletedAt: entry.deletedAt ?? null,
|
||||
},
|
||||
]),
|
||||
),
|
||||
universalIdentifierById: Object.fromEntries(
|
||||
@@ -713,6 +732,196 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSystemSideEffect inheritance from parent view', () => {
|
||||
it('should flag the created view field when the parent view is a system side effect', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
|
||||
buildFieldsWidget(),
|
||||
]),
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: VIEW_ID,
|
||||
universalIdentifier: VIEW_UNIVERSAL_IDENTIFIER,
|
||||
isSystemSideEffect: true,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].isSystemSideEffect).toBe(true);
|
||||
});
|
||||
|
||||
it('should not flag the created view field when the parent view is not a system side effect', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
|
||||
buildFieldsWidget(),
|
||||
]),
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: VIEW_ID,
|
||||
universalIdentifier: VIEW_UNIVERSAL_IDENTIFIER,
|
||||
isSystemSideEffect: false,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].isSystemSideEffect).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('INDEX view propagation', () => {
|
||||
it('should add a hidden, flagged view field to the object INDEX view even without a fields widget', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps:
|
||||
buildEmptyFlatEntityMaps() as unknown as FlatPageLayoutWidgetMaps,
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: 'index-view-db-id',
|
||||
universalIdentifier: 'index-view-uid',
|
||||
key: ViewKey.INDEX,
|
||||
isSystemSideEffect: true,
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchObject({
|
||||
viewUniversalIdentifier: 'index-view-uid',
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
isVisible: false,
|
||||
isSystemSideEffect: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not target a standalone non-INDEX view without a widget', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps:
|
||||
buildEmptyFlatEntityMaps() as unknown as FlatPageLayoutWidgetMaps,
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: 'plain-view-db-id',
|
||||
universalIdentifier: 'plain-view-uid',
|
||||
key: null,
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should skip an inactive or soft-deleted INDEX view', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps:
|
||||
buildEmptyFlatEntityMaps() as unknown as FlatPageLayoutWidgetMaps,
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: 'inactive-index-view-db-id',
|
||||
universalIdentifier: 'inactive-index-view-uid',
|
||||
key: ViewKey.INDEX,
|
||||
isActive: false,
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
{
|
||||
id: 'deleted-index-view-db-id',
|
||||
universalIdentifier: 'deleted-index-view-uid',
|
||||
key: ViewKey.INDEX,
|
||||
deletedAt: '2024-01-01T00:00:00.000Z',
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not duplicate a field when a fields widget and the INDEX view share the same view', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
fieldsToCreate: [
|
||||
{
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: 'field-uid-1',
|
||||
},
|
||||
],
|
||||
flatPageLayoutWidgetMaps: buildFlatPageLayoutWidgetMaps([
|
||||
buildFieldsWidget({ viewId: 'index-view-db-id', isVisible: true }),
|
||||
]),
|
||||
flatViewFieldMaps: buildFlatViewFieldMaps(),
|
||||
flatViewMaps: buildFlatViewMaps([
|
||||
{
|
||||
id: 'index-view-db-id',
|
||||
universalIdentifier: 'index-view-uid',
|
||||
key: ViewKey.INDEX,
|
||||
objectMetadataUniversalIdentifier:
|
||||
OBJECT_METADATA_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
]),
|
||||
flatViewFieldGroupMaps: buildFlatViewFieldGroupMaps(),
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].isVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unique universal identifiers', () => {
|
||||
it('should generate unique universalIdentifier for each created view field', () => {
|
||||
const result = computeFlatViewFieldsFromFieldsWidgets({
|
||||
|
||||
+77
-26
@@ -1,8 +1,8 @@
|
||||
import { ViewKey } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatViewFieldGroupMaps } from 'src/engine/metadata-modules/flat-view-field-group/types/flat-view-field-group-maps.type';
|
||||
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant';
|
||||
import { type FlatViewFieldMaps } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field-maps.type';
|
||||
@@ -19,6 +19,11 @@ type FieldToCreateInfo = {
|
||||
fieldMetadataUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
type FieldViewTarget = {
|
||||
viewId: string;
|
||||
isVisible: boolean;
|
||||
};
|
||||
|
||||
const isFieldsWidgetConfiguration = (
|
||||
configuration: AllPageLayoutWidgetConfiguration,
|
||||
): configuration is FieldsConfigurationDTO => {
|
||||
@@ -28,25 +33,65 @@ const isFieldsWidgetConfiguration = (
|
||||
);
|
||||
};
|
||||
|
||||
const getMatchingFieldsWidgets = ({
|
||||
const getFieldViewTargets = ({
|
||||
objectMetadataUniversalIdentifier,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatViewMaps,
|
||||
}: {
|
||||
objectMetadataUniversalIdentifier: string;
|
||||
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
|
||||
}): FlatPageLayoutWidget[] =>
|
||||
Object.values(flatPageLayoutWidgetMaps.byUniversalIdentifier)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
widget.isActive &&
|
||||
widget.type === WidgetType.FIELDS &&
|
||||
widget.objectMetadataUniversalIdentifier ===
|
||||
objectMetadataUniversalIdentifier &&
|
||||
isFieldsWidgetConfiguration(widget.configuration) &&
|
||||
isDefined(widget.configuration.viewId) &&
|
||||
isDefined(widget.configuration.newFieldDefaultVisibility),
|
||||
);
|
||||
flatViewMaps: FlatViewMaps;
|
||||
}): FieldViewTarget[] => {
|
||||
const targets: FieldViewTarget[] = [];
|
||||
const seenViewIds = new Set<string>();
|
||||
|
||||
for (const widget of Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
).filter(isDefined)) {
|
||||
if (
|
||||
!widget.isActive ||
|
||||
widget.type !== WidgetType.FIELDS ||
|
||||
widget.objectMetadataUniversalIdentifier !==
|
||||
objectMetadataUniversalIdentifier ||
|
||||
!isFieldsWidgetConfiguration(widget.configuration)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { viewId, newFieldDefaultVisibility } = widget.configuration;
|
||||
|
||||
if (
|
||||
!isDefined(viewId) ||
|
||||
!isDefined(newFieldDefaultVisibility) ||
|
||||
seenViewIds.has(viewId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenViewIds.add(viewId);
|
||||
targets.push({ viewId, isVisible: newFieldDefaultVisibility });
|
||||
}
|
||||
|
||||
for (const view of Object.values(flatViewMaps.byUniversalIdentifier).filter(
|
||||
isDefined,
|
||||
)) {
|
||||
if (
|
||||
view.key !== ViewKey.INDEX ||
|
||||
!view.isActive ||
|
||||
isDefined(view.deletedAt) ||
|
||||
view.objectMetadataUniversalIdentifier !==
|
||||
objectMetadataUniversalIdentifier ||
|
||||
seenViewIds.has(view.id)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seenViewIds.add(view.id);
|
||||
targets.push({ viewId: view.id, isVisible: false });
|
||||
}
|
||||
|
||||
return targets;
|
||||
};
|
||||
|
||||
const findLastViewFieldGroupId = ({
|
||||
viewId,
|
||||
@@ -130,11 +175,13 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
];
|
||||
|
||||
const nextPositionByKey = new Map<string, number>();
|
||||
const queuedViewFieldKeys = new Set<string>();
|
||||
|
||||
for (const objectMetadataUniversalIdentifier of objectMetadataUniversalIdentifiers) {
|
||||
const matchingWidgets = getMatchingFieldsWidgets({
|
||||
const targets = getFieldViewTargets({
|
||||
objectMetadataUniversalIdentifier,
|
||||
flatPageLayoutWidgetMaps,
|
||||
flatViewMaps,
|
||||
});
|
||||
|
||||
const fieldsForObject = fieldsToCreate.filter(
|
||||
@@ -143,16 +190,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
objectMetadataUniversalIdentifier,
|
||||
);
|
||||
|
||||
for (const widget of matchingWidgets) {
|
||||
if (!isFieldsWidgetConfiguration(widget.configuration)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const configuration = widget.configuration;
|
||||
|
||||
const viewId = configuration.viewId!;
|
||||
const isVisible = configuration.newFieldDefaultVisibility!;
|
||||
|
||||
for (const { viewId, isVisible } of targets) {
|
||||
const viewUniversalIdentifier =
|
||||
flatViewMaps.universalIdentifierById[viewId] ?? null;
|
||||
|
||||
@@ -160,6 +198,10 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
continue;
|
||||
}
|
||||
|
||||
const isSystemSideEffect =
|
||||
flatViewMaps.byUniversalIdentifier[viewUniversalIdentifier]
|
||||
?.isSystemSideEffect ?? false;
|
||||
|
||||
const viewFieldGroupId = findLastViewFieldGroupId({
|
||||
viewId,
|
||||
flatViewFieldGroupMaps,
|
||||
@@ -184,6 +226,14 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
}
|
||||
|
||||
for (const field of fieldsForObject) {
|
||||
const dedupKey = `${viewId}:${field.fieldMetadataUniversalIdentifier}`;
|
||||
|
||||
if (queuedViewFieldKeys.has(dedupKey)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
queuedViewFieldKeys.add(dedupKey);
|
||||
|
||||
const position = nextPositionByKey.get(positionKey)!;
|
||||
|
||||
nextPositionByKey.set(positionKey, position + 1);
|
||||
@@ -200,6 +250,7 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({
|
||||
position,
|
||||
aggregateOperation: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect,
|
||||
universalOverrides: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
|
||||
position: createViewFieldInput.position ?? 0,
|
||||
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
universalOverrides: null,
|
||||
viewFieldGroupUniversalIdentifier,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewFieldToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingFlatViewFieldToUpdate.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+1
@@ -87,6 +87,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
|
||||
visibility: createViewInput.visibility ?? ViewVisibility.WORKSPACE,
|
||||
createdByUserWorkspaceId: createdByUserWorkspaceId ?? null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
universalOverrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
viewFilterUniversalIdentifiers: [],
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ export const fromDeleteViewInputToFlatViewOrThrow = ({
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewToDelete.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingFlatViewToDelete.isSystemSideEffect,
|
||||
});
|
||||
|
||||
if (shouldDeactivate) {
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingFlatViewToUpdate.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingFlatViewToUpdate.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties } =
|
||||
|
||||
+8
@@ -12,6 +12,8 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -78,4 +80,10 @@ export class IndexMetadataEntity
|
||||
nullable: false,
|
||||
})
|
||||
indexType: IndexType;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
}
|
||||
|
||||
+1
@@ -243,6 +243,7 @@ export class IndexMetadataService {
|
||||
indexWhereClause: null,
|
||||
isCustom: true,
|
||||
isUnique: false,
|
||||
isSystemSideEffect: false,
|
||||
objectMetadataUniversalIdentifier:
|
||||
flatObjectMetadata.universalIdentifier,
|
||||
universalIdentifier: indexMetadataUniversalIdentifier,
|
||||
|
||||
+1
@@ -65,6 +65,7 @@ describe('generateFlatIndexMetadataWithNameOrThrow', () => {
|
||||
indexWhereClause: overrides.indexWhereClause ?? null,
|
||||
isUnique: overrides.isUnique,
|
||||
isCustom: false,
|
||||
isSystemSideEffect: false,
|
||||
universalFlatIndexFieldMetadatas: overrides.fieldIds.map((id, order) => ({
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+8
@@ -23,6 +23,7 @@ const PARTIAL_ID_FIELD = {
|
||||
isUnique: true,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: 'uuid',
|
||||
@@ -51,6 +52,7 @@ const PARTIAL_CREATED_AT_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: 'now',
|
||||
@@ -79,6 +81,7 @@ const PARTIAL_UPDATED_AT_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: 'now',
|
||||
@@ -107,6 +110,7 @@ const PARTIAL_DELETED_AT_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: null,
|
||||
@@ -135,6 +139,7 @@ const PARTIAL_CREATED_BY_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: { name: "''", source: "'MANUAL'" },
|
||||
@@ -163,6 +168,7 @@ const PARTIAL_UPDATED_BY_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: { name: "''", source: "'MANUAL'" },
|
||||
@@ -191,6 +197,7 @@ const PARTIAL_POSITION_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: 0,
|
||||
@@ -219,6 +226,7 @@ const PARTIAL_SEARCH_VECTOR_FIELD = {
|
||||
isUnique: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: false,
|
||||
isLabelSyncedWithName: false,
|
||||
defaultValue: null,
|
||||
|
||||
+71
-100
@@ -145,7 +145,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
existingFlatObjectMetadata,
|
||||
flatCommandMenuItemMaps: existingFlatCommandMenuItemMaps,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
applicationId: resolvedOwnerFlatApplication.id,
|
||||
applicationUniversalIdentifier:
|
||||
resolvedOwnerFlatApplication.universalIdentifier,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
@@ -465,7 +467,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
workspaceId: string;
|
||||
ownerFlatApplication?: FlatApplication;
|
||||
}): Promise<FlatObjectMetadata> {
|
||||
const { workspaceCustomFlatApplication, twentyStandardFlatApplication } =
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
@@ -497,7 +499,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
});
|
||||
|
||||
const flatDefaultViewFieldsToCreate = computeFlatViewFieldsToCreate({
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
objectFlatFieldMetadatas: flatFieldMetadataToCreateOnObject,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
@@ -525,11 +527,38 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
{
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
workspaceId,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
applicationId: resolvedOwnerFlatApplication.id,
|
||||
applicationUniversalIdentifier:
|
||||
resolvedOwnerFlatApplication.universalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
},
|
||||
);
|
||||
|
||||
const flatRecordPageFieldsViewToCreate =
|
||||
this.computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
});
|
||||
|
||||
const flatRecordPageFieldsViewFieldsToCreate =
|
||||
computeFlatViewFieldsToCreate({
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
objectFlatFieldMetadatas: flatFieldMetadataToCreateOnObject,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier:
|
||||
flatRecordPageFieldsViewToCreate.universalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
const flatDefaultRecordPageLayoutsToCreate =
|
||||
this.computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsViewToCreate,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
@@ -540,12 +569,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: [flatDefaultViewToCreate],
|
||||
flatEntityToCreate: [
|
||||
flatDefaultViewToCreate,
|
||||
flatRecordPageFieldsViewToCreate,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: flatDefaultViewFieldsToCreate,
|
||||
flatEntityToCreate: [
|
||||
...flatDefaultViewFieldsToCreate,
|
||||
...flatRecordPageFieldsViewFieldsToCreate,
|
||||
],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
@@ -562,6 +597,29 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [flatCommandMenuItemToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayout: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayouts,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayoutTabs,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayoutWidgets,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
...(isDefined(flatNavigationMenuItemToCreate)
|
||||
? {
|
||||
navigationMenuItem: {
|
||||
@@ -586,100 +644,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
);
|
||||
}
|
||||
|
||||
const commandMenuItemMigrationResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
commandMenuItem: {
|
||||
flatEntityToCreate: [flatCommandMenuItemToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (commandMenuItemMigrationResult.status === 'fail') {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
commandMenuItemMigrationResult,
|
||||
'Multiple validation errors occurred while creating command menu item',
|
||||
);
|
||||
}
|
||||
|
||||
const flatRecordPageFieldsViewToCreate =
|
||||
this.computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: twentyStandardFlatApplication,
|
||||
});
|
||||
|
||||
const flatRecordPageFieldsViewFieldsToCreate =
|
||||
computeFlatViewFieldsToCreate({
|
||||
flatApplication: twentyStandardFlatApplication,
|
||||
objectFlatFieldMetadatas: flatFieldMetadataToCreateOnObject,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
flatObjectMetadataToCreate.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
viewUniversalIdentifier:
|
||||
flatRecordPageFieldsViewToCreate.universalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
const flatDefaultRecordPageLayoutsToCreate =
|
||||
this.computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: flatObjectMetadataToCreate,
|
||||
flatApplication: twentyStandardFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsViewToCreate,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const pageLayoutMigrationResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
view: {
|
||||
flatEntityToCreate: [flatRecordPageFieldsViewToCreate],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: flatRecordPageFieldsViewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayout: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayouts,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayoutTabs,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate:
|
||||
flatDefaultRecordPageLayoutsToCreate.pageLayoutWidgets,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (pageLayoutMigrationResult.status === 'fail') {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
pageLayoutMigrationResult,
|
||||
'Multiple validation errors occurred while creating page layouts for object',
|
||||
);
|
||||
}
|
||||
|
||||
const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
@@ -737,6 +701,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
visibility: ViewVisibility.WORKSPACE,
|
||||
createdByUserWorkspaceId: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
universalOverrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
viewFieldGroupUniversalIdentifiers: [],
|
||||
@@ -847,6 +812,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
objectMetadata,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
}: {
|
||||
objectMetadata: {
|
||||
@@ -859,6 +825,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
};
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
flatCommandMenuItemMaps: {
|
||||
byUniversalIdentifier: Record<string, FlatCommandMenuItem | undefined>;
|
||||
};
|
||||
@@ -876,6 +843,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
objectMetadata,
|
||||
commandMenuItemId: v4(),
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
workspaceId,
|
||||
position: nextPosition,
|
||||
now: new Date().toISOString(),
|
||||
@@ -909,6 +877,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
flatCommandMenuItemMaps,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
isBeingEnabled: boolean;
|
||||
isBeingDisabled: boolean;
|
||||
@@ -918,6 +887,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
};
|
||||
workspaceId: string;
|
||||
applicationId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): {
|
||||
commandMenuItemsToCreate: FlatCommandMenuItem[];
|
||||
commandMenuItemsToUpdate: FlatCommandMenuItem[];
|
||||
@@ -943,6 +913,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
objectMetadata: existingFlatObjectMetadata,
|
||||
workspaceId,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
flatCommandMenuItemMaps,
|
||||
}),
|
||||
],
|
||||
|
||||
+1
@@ -134,6 +134,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
|
||||
isNullable: true,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isSystemSideEffect: true,
|
||||
isUIEditable: true,
|
||||
defaultValue: null,
|
||||
createdAt: now,
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ export const buildDefaultIndexesForCustomObject = ({
|
||||
indexWhereClause: null,
|
||||
isCustom: false,
|
||||
isUnique: false,
|
||||
isSystemSideEffect: true,
|
||||
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
|
||||
universalIdentifier: tsFlatVectorIndexUniversalIdentifier,
|
||||
updatedAt: createdAt.toISOString(),
|
||||
|
||||
+3
@@ -64,6 +64,7 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
|
||||
widgetIds: [widgetId],
|
||||
widgetUniversalIdentifiers: [widgetUniversalIdentifier],
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
@@ -119,6 +120,7 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
|
||||
objectMetadataId: objectMetadata.id,
|
||||
objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier,
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
@@ -141,6 +143,7 @@ export const computeFlatDefaultRecordPageLayoutToCreate = ({
|
||||
tabUniversalIdentifiers: pageLayoutTabs.map(
|
||||
(tab) => tab.universalIdentifier,
|
||||
),
|
||||
isSystemSideEffect: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ export const computeFlatRecordPageFieldsViewToCreate = ({
|
||||
visibility: ViewVisibility.WORKSPACE,
|
||||
createdByUserWorkspaceId: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
universalOverrides: null,
|
||||
viewFieldUniversalIdentifiers: [],
|
||||
viewFieldGroupUniversalIdentifiers: [],
|
||||
|
||||
+1
@@ -65,6 +65,7 @@ export const computeFlatViewFieldsToCreate = ({
|
||||
position: index,
|
||||
aggregateOperation: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: true,
|
||||
universalOverrides: null,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
}));
|
||||
|
||||
+8
@@ -15,6 +15,8 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
|
||||
import { PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
|
||||
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
|
||||
@@ -70,6 +72,12 @@ export class PageLayoutTabEntity
|
||||
})
|
||||
layoutMode: PageLayoutTabLayoutMode;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+8
@@ -19,6 +19,8 @@ import {
|
||||
type Relation,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
@@ -100,6 +102,12 @@ export class PageLayoutWidgetEntity<
|
||||
PageLayoutWidgetConfigurationTypeSettings<TWidgetConfigurationType>
|
||||
>;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+8
@@ -14,6 +14,8 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
@@ -69,6 +71,12 @@ export class PageLayoutEntity
|
||||
@JoinColumn({ name: 'defaultTabToFocusOnMobileAndSidePanelId' })
|
||||
defaultTabToFocusOnMobileAndSidePanel: Relation<PageLayoutTabEntity> | null;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+6
-3
@@ -112,7 +112,8 @@ export class PageLayoutResetService {
|
||||
|
||||
if (
|
||||
widget.applicationUniversalIdentifier ===
|
||||
workspaceCustomFlatApplication.universalIdentifier
|
||||
workspaceCustomFlatApplication.universalIdentifier &&
|
||||
!widget.isSystemSideEffect
|
||||
) {
|
||||
throw new PageLayoutWidgetException(
|
||||
`Custom widget "${id}" cannot be reset to default`,
|
||||
@@ -255,7 +256,8 @@ export class PageLayoutResetService {
|
||||
|
||||
if (
|
||||
tab.applicationUniversalIdentifier ===
|
||||
workspaceCustomFlatApplication.universalIdentifier
|
||||
workspaceCustomFlatApplication.universalIdentifier &&
|
||||
!tab.isSystemSideEffect
|
||||
) {
|
||||
throw new PageLayoutTabException(
|
||||
`Custom tab "${id}" cannot be reset to default`,
|
||||
@@ -404,7 +406,8 @@ export class PageLayoutResetService {
|
||||
|
||||
if (
|
||||
layout.applicationUniversalIdentifier ===
|
||||
workspaceCustomFlatApplication.universalIdentifier
|
||||
workspaceCustomFlatApplication.universalIdentifier &&
|
||||
!layout.isSystemSideEffect
|
||||
) {
|
||||
throw new PageLayoutException(
|
||||
`Custom page layout "${id}" cannot be reset to default`,
|
||||
|
||||
+5
@@ -328,6 +328,7 @@ export class PageLayoutUpdateService {
|
||||
layoutMode: tabInput.layoutMode ?? PageLayoutTabLayoutMode.GRID,
|
||||
overrides: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -345,6 +346,7 @@ export class PageLayoutUpdateService {
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingTab.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingTab.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const editableProperties = {
|
||||
@@ -384,6 +386,7 @@ export class PageLayoutUpdateService {
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingTab.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingTab.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const editableProperties = {
|
||||
@@ -605,6 +608,7 @@ export class PageLayoutUpdateService {
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
universalConfiguration:
|
||||
fromPageLayoutWidgetConfigurationToUniversalConfiguration({
|
||||
configuration: widgetInput.configuration,
|
||||
@@ -721,6 +725,7 @@ export class PageLayoutUpdateService {
|
||||
entityApplicationUniversalIdentifier:
|
||||
existingWidget.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingWidget.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const configuration = widgetInput.configuration ?? null;
|
||||
|
||||
+3
@@ -11,6 +11,7 @@ describe('isCallerOverridingEntity', () => {
|
||||
callerApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
entityApplicationUniversalIdentifier: STANDARD_APP_ID,
|
||||
workspaceCustomApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
isSystemSideEffect: false,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@@ -21,6 +22,7 @@ describe('isCallerOverridingEntity', () => {
|
||||
callerApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
entityApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
workspaceCustomApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
isSystemSideEffect: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
@@ -31,6 +33,7 @@ describe('isCallerOverridingEntity', () => {
|
||||
callerApplicationUniversalIdentifier: OTHER_APP_ID,
|
||||
entityApplicationUniversalIdentifier: STANDARD_APP_ID,
|
||||
workspaceCustomApplicationUniversalIdentifier: CUSTOM_APP_ID,
|
||||
isSystemSideEffect: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
+5
-2
@@ -2,15 +2,18 @@ export const isCallerOverridingEntity = ({
|
||||
callerApplicationUniversalIdentifier,
|
||||
entityApplicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier,
|
||||
isSystemSideEffect,
|
||||
}: {
|
||||
callerApplicationUniversalIdentifier: string;
|
||||
entityApplicationUniversalIdentifier: string;
|
||||
workspaceCustomApplicationUniversalIdentifier: string;
|
||||
isSystemSideEffect: boolean;
|
||||
}): boolean => {
|
||||
return (
|
||||
callerApplicationUniversalIdentifier ===
|
||||
workspaceCustomApplicationUniversalIdentifier &&
|
||||
entityApplicationUniversalIdentifier !==
|
||||
workspaceCustomApplicationUniversalIdentifier
|
||||
(entityApplicationUniversalIdentifier !==
|
||||
workspaceCustomApplicationUniversalIdentifier ||
|
||||
isSystemSideEffect)
|
||||
);
|
||||
};
|
||||
|
||||
+7
@@ -250,6 +250,7 @@ export class FieldsWidgetUpsertService {
|
||||
existingGroup.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: false,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedGroupProps } =
|
||||
@@ -349,6 +350,7 @@ export class FieldsWidgetUpsertService {
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingField.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
@@ -435,6 +437,7 @@ export class FieldsWidgetUpsertService {
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingField.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
@@ -530,6 +533,7 @@ export class FieldsWidgetUpsertService {
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
@@ -641,6 +645,7 @@ export class FieldsWidgetUpsertService {
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingField.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
@@ -700,6 +705,7 @@ export class FieldsWidgetUpsertService {
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingField.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
@@ -788,6 +794,7 @@ export class FieldsWidgetUpsertService {
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+8
@@ -15,7 +15,9 @@ import {
|
||||
type SerializedRelation,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { OverridableEntity } from 'src/engine/workspace-manager/types/overridable-entity';
|
||||
@@ -80,6 +82,12 @@ export class ViewFieldEntity
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
viewFieldGroupId: string | null;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-15/is-system-side-effect-upgrade-command-name.constant';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldGroupEntity } from 'src/engine/metadata-modules/view-field-group/entities/view-field-group.entity';
|
||||
@@ -204,6 +205,12 @@ export class ViewEntity
|
||||
})
|
||||
visibility: ViewVisibility;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName: ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME,
|
||||
})
|
||||
@Column({ nullable: false, default: false, type: 'boolean' })
|
||||
isSystemSideEffect: boolean;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
createdByUserWorkspaceId: string | null;
|
||||
|
||||
|
||||
+2
@@ -443,6 +443,7 @@ export class ViewWidgetUpsertService {
|
||||
existingField.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
applicationUniversalIdentifier,
|
||||
isSystemSideEffect: existingField.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const { overrides, updatedEditableProperties: sanitizedFieldProps } =
|
||||
@@ -523,6 +524,7 @@ export class ViewWidgetUpsertService {
|
||||
overrides: null,
|
||||
universalOverrides: null,
|
||||
isActive: true,
|
||||
isSystemSideEffect: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
@@ -329,6 +329,7 @@ export class ViewService {
|
||||
existingFlatView.applicationUniversalIdentifier,
|
||||
workspaceCustomApplicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
isSystemSideEffect: existingFlatView.isSystemSideEffect,
|
||||
});
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
Reference in New Issue
Block a user