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:
Weiko
2026-06-17 15:56:27 +02:00
committed by GitHub
parent 57d15fa73a
commit 3ee93b5ec9
102 changed files with 4572 additions and 530 deletions
@@ -197,6 +197,8 @@ export class RefactorNavigationCommandsCommand extends ActiveOrSuspendedWorkspac
objectMetadata,
commandMenuItemId: v4(),
applicationId: twentyStandardFlatApplication.id,
applicationUniversalIdentifier:
twentyStandardFlatApplication.universalIdentifier,
workspaceId,
position: nextPosition++,
now,
@@ -222,6 +224,7 @@ export class RefactorNavigationCommandsCommand extends ActiveOrSuspendedWorkspac
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION.universalIdentifier,
workspaceId,
isSystemSideEffect: false,
label: commandMenuItem.label,
shortLabel: commandMenuItem.shortLabel,
icon: commandMenuItem.icon,
@@ -122,6 +122,7 @@ const buildLegacyCalendarEventRecordingPreferenceFieldMetadata = ({
icon: 'IconSettingsAutomation',
isActive: true,
isSystem: false,
isSystemSideEffect: false,
isNullable: false,
isUnique: false,
isUIEditable: true,
@@ -1,3 +1,4 @@
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { v5 } from 'uuid';
import { buildNavigationCommandMenuItemOperationsOrThrow } from 'src/database/commands/upgrade-version-command/2-10/utils/build-navigation-command-menu-item-operations-or-throw.util';
@@ -51,6 +52,8 @@ const buildExistingNavigationItem = ({
},
commandMenuItemId: `command-menu-item-${objectUniversalIdentifier}`,
applicationId: APPLICATION_ID,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
workspaceId: WORKSPACE_ID,
position,
now: NOW,
@@ -1,3 +1,4 @@
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { isDefined } from 'twenty-shared/utils';
import { v4, v5 } from 'uuid';
@@ -63,6 +64,8 @@ export const buildNavigationCommandMenuItemOperationsOrThrow = ({
objectMetadata,
commandMenuItemId: v4(),
applicationId,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
workspaceId,
position: nextPosition++,
now,
@@ -0,0 +1,36 @@
import { QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
const TABLES_WITH_IS_SYSTEM_SIDE_EFFECT = [
'view',
'viewField',
'indexMetadata',
'commandMenuItem',
'pageLayout',
'pageLayoutTab',
'pageLayoutWidget',
'fieldMetadata',
] as const;
@RegisteredInstanceCommand('2.15.0', 1781600000000)
export class AddIsSystemSideEffectFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
for (const table of TABLES_WITH_IS_SYSTEM_SIDE_EFFECT) {
await queryRunner.query(
`ALTER TABLE "core"."${table}" ADD COLUMN IF NOT EXISTS "isSystemSideEffect" boolean NOT NULL DEFAULT false`,
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
for (const table of TABLES_WITH_IS_SYSTEM_SIDE_EFFECT) {
await queryRunner.query(
`ALTER TABLE "core"."${table}" DROP COLUMN IF EXISTS "isSystemSideEffect"`,
);
}
}
}
@@ -0,0 +1,2 @@
export const ADD_IS_SYSTEM_SIDE_EFFECT_UPGRADE_COMMAND_NAME =
'2.15.0_AddIsSystemSideEffectFastInstanceCommand_1781600000000';
@@ -18,6 +18,19 @@ import { DropWorkspaceVersionColumnFastInstanceCommand } from 'src/database/comm
import { AddIsPreInstalledToApplicationRegistrationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-1/2-1-instance-command-fast-1776886452831-add-is-pre-installed-to-application-registration';
import { AddProviderExecutedToAgentMessagePartFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-1/2-1-instance-command-fast-1777012800000-add-provider-executed-to-agent-message-part';
import { BackfillPageLayoutWidgetPositionSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-1/2-1-instance-command-slow-1795000002000-backfill-page-layout-widget-position';
import { DropEmailingDomainDriverColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-11/2-11-instance-command-fast-1780926908000-drop-emailing-domain-driver-column';
import { DropIsCustomFromObjectAndFieldMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-12/2-12-instance-command-fast-1780579070012-drop-is-custom-from-object-and-field-metadata';
import { ViewOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-12/2-12-instance-command-fast-1781114009075-view-overridable-entity';
import { AddEmailingDomainUnsubscribeHostFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1780088214774-add-emailing-domain-unsubscribe-host';
import { AddArchivedAtToConnectedAccountFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781171103000-add-archived-at-to-connected-account';
import { CreateMessageSuppressionCoreTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781250000000-create-message-suppression-core-table';
import { CommandMenuItemOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781253016028-command-menu-item-overridable-entity';
import { CreateUnsubscribeTopicCoreTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781260000000-create-unsubscribe-topic-core-table';
import { RenameIsUiReadOnlyToIsUiEditableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781277453604-rename-is-ui-read-only-to-is-ui-editable';
import { BackfillNonUiCreatableStandardSystemObjectsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-slow-1781277480000-backfill-non-ui-creatable-standard-system-objects';
import { SetTableWidgetViewsVisibilityToWorkspaceSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-14/2-14-instance-command-slow-1781515653781-set-table-widget-views-visibility-to-workspace';
import { AddIsSystemSideEffectFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-15/2-15-instance-command-fast-1781600000000-add-is-system-side-effect';
import { BackfillConnectionSecuritySlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-15/2-15-instance-command-slow-1781461753981-backfill-connection-security';
import { AddCacheTokensToAgentChatThreadFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777455269302-add-cache-tokens-to-agent-chat-thread';
import { AddLogoToApplicationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777539664664-add-logo-to-application';
import { AddSubFieldNameToViewSortEarlyFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1747234200000-add-sub-field-name-to-view-sort';
@@ -59,18 +72,6 @@ import { EmailingDomainTenantStatusAndGlobalUniquenessFastInstanceCommand } from
import { AddLogicFunctionExecutionModeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-fast-1799000030000-add-logic-function-execution-mode';
import { EncryptNonSecretApplicationVariableSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1798400000000-encrypt-non-secret-application-variable';
import { MigrateAiModelPreferencesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1799000010000-migrate-ai-model-preferences';
import { DropIsCustomFromObjectAndFieldMetadataFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-12/2-12-instance-command-fast-1780579070012-drop-is-custom-from-object-and-field-metadata';
import { AddArchivedAtToConnectedAccountFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781171103000-add-archived-at-to-connected-account';
import { DropEmailingDomainDriverColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-11/2-11-instance-command-fast-1780926908000-drop-emailing-domain-driver-column';
import { AddEmailingDomainUnsubscribeHostFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1780088214774-add-emailing-domain-unsubscribe-host';
import { CreateMessageSuppressionCoreTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781250000000-create-message-suppression-core-table';
import { CreateUnsubscribeTopicCoreTableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781260000000-create-unsubscribe-topic-core-table';
import { ViewOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-12/2-12-instance-command-fast-1781114009075-view-overridable-entity';
import { RenameIsUiReadOnlyToIsUiEditableFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781277453604-rename-is-ui-read-only-to-is-ui-editable';
import { BackfillNonUiCreatableStandardSystemObjectsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-slow-1781277480000-backfill-non-ui-creatable-standard-system-objects';
import { CommandMenuItemOverridableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-13/2-13-instance-command-fast-1781253016028-command-menu-item-overridable-entity';
import { SetTableWidgetViewsVisibilityToWorkspaceSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-14/2-14-instance-command-slow-1781515653781-set-table-widget-views-visibility-to-workspace';
import { BackfillConnectionSecuritySlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-15/2-15-instance-command-slow-1781461753981-backfill-connection-security';
export const INSTANCE_COMMANDS = [
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
@@ -143,5 +144,6 @@ export const INSTANCE_COMMANDS = [
BackfillNonUiCreatableStandardSystemObjectsSlowInstanceCommand,
CommandMenuItemOverridableEntityFastInstanceCommand,
SetTableWidgetViewsVisibilityToWorkspaceSlowInstanceCommand,
AddIsSystemSideEffectFastInstanceCommand,
BackfillConnectionSecuritySlowInstanceCommand,
];
@@ -46,6 +46,7 @@ export const fromCommandMenuItemManifestToUniversalFlatCommandMenuItem = ({
workflowVersionId: null,
pageLayoutUniversalIdentifier: null,
isActive: true,
isSystemSideEffect: false,
universalOverrides: null,
createdAt: now,
updatedAt: now,
@@ -94,6 +94,8 @@ export const fromFieldManifestToUniversalFlatFieldMetadata = ({
universalSettings: fieldManifest.universalSettings ?? null,
isActive: true,
isSystem: fieldManifest.name in PARTIAL_SYSTEM_FLAT_FIELD_METADATAS,
isSystemSideEffect:
fieldManifest.name in PARTIAL_SYSTEM_FLAT_FIELD_METADATAS,
isUIEditable: fieldManifest.isUIEditable ?? true,
isNullable: fieldManifest.isNullable ?? true,
isUnique: fieldManifest.isUnique ?? false,
@@ -129,6 +129,7 @@ export const fromIndexManifestToUniversalFlatIndex = ({
indexWhereClause: null,
isCustom: false,
isUnique: indexManifest.isUnique ?? false,
isSystemSideEffect: false,
universalFlatIndexFieldMetadatas,
},
});
@@ -24,6 +24,7 @@ export const fromPageLayoutManifestToUniversalFlatPageLayout = ({
pageLayoutManifest.defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier ??
null,
tabUniversalIdentifiers: [],
isSystemSideEffect: false,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -24,6 +24,7 @@ export const fromPageLayoutTabManifestToUniversalFlatPageLayoutTab = ({
layoutMode:
pageLayoutTabManifest.layoutMode ?? PageLayoutTabLayoutMode.GRID,
isActive: true,
isSystemSideEffect: false,
widgetUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
@@ -21,6 +21,7 @@ export const fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget = ({
pageLayoutTabUniversalIdentifier,
title: pageLayoutWidgetManifest.title,
isActive: true,
isSystemSideEffect: false,
type: pageLayoutWidgetManifest.type as WidgetType,
objectMetadataUniversalIdentifier:
pageLayoutWidgetManifest.objectUniversalIdentifier ?? null,
@@ -23,6 +23,7 @@ export const fromViewFieldManifestToUniversalFlatViewField = ({
viewFieldManifest.viewFieldGroupUniversalIdentifier ?? null,
isVisible: viewFieldManifest.isVisible ?? true,
isActive: true,
isSystemSideEffect: false,
size: viewFieldManifest.size ?? 0,
position: viewFieldManifest.position,
aggregateOperation: viewFieldManifest.aggregateOperation ?? null,
@@ -42,6 +42,7 @@ export const fromViewManifestToUniversalFlatView = ({
anyFieldFilterValue: null,
createdByUserWorkspaceId: null,
isActive: true,
isSystemSideEffect: false,
universalOverrides: null,
viewFieldUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
@@ -356,6 +356,7 @@ export class CommandMenuItemService {
flatCommandMenuItemToDelete.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
isSystemSideEffect: flatCommandMenuItemToDelete.isSystemSideEffect,
});
const deactivatedFlatCommandMenuItem = {
@@ -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;
@@ -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(),
};
@@ -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,
@@ -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', () => {
@@ -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,
@@ -116,6 +116,7 @@ export const fromCommandMenuItemEntityToFlatCommandMenuItem = ({
pageLayoutId: commandMenuItemEntity.pageLayoutId,
pageLayoutUniversalIdentifier,
isActive: commandMenuItemEntity.isActive,
isSystemSideEffect: commandMenuItemEntity.isSystemSideEffect,
overrides: commandMenuItemEntity.overrides,
universalOverrides,
};
@@ -77,6 +77,7 @@ export const fromCreateCommandMenuItemInputToFlatCommandMenuItemToCreate = ({
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
isActive: true,
isSystemSideEffect: false,
overrides: null,
universalOverrides: null,
createdAt: now,
@@ -53,6 +53,7 @@ export const fromUpdateCommandMenuItemInputToFlatCommandMenuItemToUpdateOrThrow
entityApplicationUniversalIdentifier:
existingFlatCommandMenuItem.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: existingFlatCommandMenuItem.isSystemSideEffect,
});
const { overrides, updatedEditableProperties } =
@@ -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,
@@ -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 {
@@ -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 {
@@ -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,
@@ -32,6 +32,7 @@ export const getRelationTargetFlatFieldMetadataMock = ({
const createdAt = '2024-01-01T00:00:00.000Z';
return {
isSystemSideEffect: false,
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
viewFilterIds: [],
@@ -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>,
@@ -38,6 +38,7 @@ export const generateIndexForFlatFieldMetadata = ({
indexWhereClause: null,
isCustom: true,
isUnique: flatFieldMetadata.isUnique ?? false,
isSystemSideEffect: true,
objectMetadataUniversalIdentifier:
flatObjectMetadata.universalIdentifier,
universalIdentifier: indexMetadataUniversalIdentifier,
@@ -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,
@@ -27,6 +27,7 @@ export const getFlatIndexMetadataMock = (
indexWhereClause: null,
isCustom: false,
isUnique: false,
isSystemSideEffect: false,
name: 'defaultFlatIndexMetadataName',
updatedAt: createdAt,
workspaceId: faker.string.uuid(),
@@ -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);
@@ -45,6 +45,7 @@ export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({
title: createPageLayoutTabInput.title,
position: createPageLayoutTabInput.position ?? 0,
isActive: true,
isSystemSideEffect: false,
pageLayoutId: createPageLayoutTabInput.pageLayoutId,
pageLayoutUniversalIdentifier,
workspaceId,
@@ -60,6 +60,7 @@ export const fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow = ({
entityApplicationUniversalIdentifier:
existingFlatPageLayoutTabToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: existingFlatPageLayoutTabToUpdate.isSystemSideEffect,
});
const { overrides, updatedEditableProperties } =
@@ -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,
@@ -65,6 +65,7 @@ export const fromCreatePageLayoutWidgetInputToFlatPageLayoutWidgetToCreate = ({
id: pageLayoutWidgetId,
...commonProperties,
isActive: true,
isSystemSideEffect: false,
workspaceId,
createdAt,
updatedAt: createdAt,
@@ -89,6 +89,8 @@ export const fromUpdatePageLayoutWidgetInputToFlatPageLayoutWidgetToUpdateOrThro
entityApplicationUniversalIdentifier:
existingFlatPageLayoutWidgetToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect:
existingFlatPageLayoutWidgetToUpdate.isSystemSideEffect,
});
const { overrides, updatedEditableProperties } =
@@ -44,6 +44,7 @@ export const fromCreatePageLayoutInputToFlatPageLayoutToCreate = ({
type: createPageLayoutInput.type ?? PageLayoutType.RECORD_PAGE,
objectMetadataId: createPageLayoutInput.objectMetadataId ?? null,
objectMetadataUniversalIdentifier,
isSystemSideEffect: false,
workspaceId,
createdAt,
updatedAt: createdAt,
@@ -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,
@@ -59,6 +59,7 @@ export const fromUpdateViewFieldGroupInputToFlatViewFieldGroupToUpdateOrThrow =
entityApplicationUniversalIdentifier:
existingFlatViewFieldGroupToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: false,
});
const { overrides, updatedEditableProperties } =
@@ -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({
@@ -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,
@@ -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,
@@ -64,6 +64,7 @@ export const fromUpdateViewFieldInputToFlatViewFieldToUpdateOrThrow = ({
entityApplicationUniversalIdentifier:
existingFlatViewFieldToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: existingFlatViewFieldToUpdate.isSystemSideEffect,
});
const { overrides, updatedEditableProperties } =
@@ -87,6 +87,7 @@ export const fromCreateViewInputToFlatViewToCreate = ({
visibility: createViewInput.visibility ?? ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: createdByUserWorkspaceId ?? null,
isActive: true,
isSystemSideEffect: false,
universalOverrides: null,
viewFieldUniversalIdentifiers: [],
viewFilterUniversalIdentifiers: [],
@@ -49,6 +49,7 @@ export const fromDeleteViewInputToFlatViewOrThrow = ({
entityApplicationUniversalIdentifier:
existingFlatViewToDelete.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: existingFlatViewToDelete.isSystemSideEffect,
});
if (shouldDeactivate) {
@@ -75,6 +75,7 @@ export const fromUpdateViewInputToFlatViewToUpdateOrThrow = ({
entityApplicationUniversalIdentifier:
existingFlatViewToUpdate.applicationUniversalIdentifier,
workspaceCustomApplicationUniversalIdentifier,
isSystemSideEffect: existingFlatViewToUpdate.isSystemSideEffect,
});
const { overrides, updatedEditableProperties } =
@@ -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;
}
@@ -243,6 +243,7 @@ export class IndexMetadataService {
indexWhereClause: null,
isCustom: true,
isUnique: false,
isSystemSideEffect: false,
objectMetadataUniversalIdentifier:
flatObjectMetadata.universalIdentifier,
universalIdentifier: indexMetadataUniversalIdentifier,
@@ -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,
@@ -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,
@@ -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,
}),
],
@@ -134,6 +134,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isNullable: true,
isActive: true,
isSystem: false,
isSystemSideEffect: true,
isUIEditable: true,
defaultValue: null,
createdAt: now,
@@ -40,6 +40,7 @@ export const buildDefaultIndexesForCustomObject = ({
indexWhereClause: null,
isCustom: false,
isUnique: false,
isSystemSideEffect: true,
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
universalIdentifier: tsFlatVectorIndexUniversalIdentifier,
updatedAt: createdAt.toISOString(),
@@ -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,
@@ -43,6 +43,7 @@ export const computeFlatRecordPageFieldsViewToCreate = ({
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
isActive: true,
isSystemSideEffect: true,
universalOverrides: null,
viewFieldUniversalIdentifiers: [],
viewFieldGroupUniversalIdentifiers: [],
@@ -65,6 +65,7 @@ export const computeFlatViewFieldsToCreate = ({
position: index,
aggregateOperation: null,
isActive: true,
isSystemSideEffect: true,
universalOverrides: null,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
}));
@@ -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;
@@ -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;
@@ -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;
@@ -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`,
@@ -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;
@@ -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);
});
@@ -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)
);
};
@@ -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,
@@ -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;
@@ -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();
@@ -146,6 +146,7 @@ describe('WorkspaceEntityManager', () => {
label: 'Field Name',
objectMetadataId: 'test-entity-id',
isNullable: true,
isSystemSideEffect: false,
isLabelSyncedWithName: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -70,6 +70,7 @@ describe('WorkspaceRepository', () => {
type: FieldMetadataType.UUID,
objectMetadataId: 'test-metadata-id',
isActive: true,
isSystemSideEffect: false,
isNullable: false,
isUnique: true,
isSystem: true,
@@ -23,6 +23,7 @@ export const getPageLayoutFlatEntitySeeds = ({
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
isSystemSideEffect: false,
name: 'Sales Dashboard Layout',
type: PageLayoutType.DASHBOARD,
objectMetadataId: null,
@@ -44,6 +45,7 @@ export const getPageLayoutFlatEntitySeeds = ({
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
isSystemSideEffect: false,
name: 'Customer Dashboard Layout',
type: PageLayoutType.DASHBOARD,
objectMetadataId: null,
@@ -65,6 +67,7 @@ export const getPageLayoutFlatEntitySeeds = ({
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
isSystemSideEffect: false,
name: 'Team Dashboard Layout',
type: PageLayoutType.DASHBOARD,
objectMetadataId: null,
@@ -89,6 +92,7 @@ export const getPageLayoutFlatEntitySeeds = ({
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
workspaceId,
isSystemSideEffect: false,
name: 'Documentation',
type: PageLayoutType.STANDALONE_PAGE,
objectMetadataId: null,
@@ -33,6 +33,7 @@ export const getPageLayoutTabFlatEntitySeeds = ({
widgetIds: [],
widgetUniversalIdentifiers: [],
isActive: true,
isSystemSideEffect: false,
icon: null,
layoutMode: PageLayoutTabLayoutMode.GRID,
overrides: null,
@@ -92,6 +92,7 @@ export const prefillFrontComponentCommandMenuItems = async ({
pageLayoutId: definition.pageLayoutId ?? null,
pageLayoutUniversalIdentifier: definition.pageLayoutId ?? null,
isActive: true,
isSystemSideEffect: false,
overrides: null,
universalOverrides: null,
createdAt: now,
@@ -71,6 +71,7 @@ export const prefillWorkflowCommandMenuItems = async ({
pageLayoutId: null,
pageLayoutUniversalIdentifier: null,
isActive: true,
isSystemSideEffect: false,
overrides: null,
universalOverrides: null,
createdAt: now,
@@ -1,3 +1,4 @@
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
@@ -72,6 +73,8 @@ export const buildStandardFlatCommandMenuItemMaps = ({
objectMetadata: flatObject,
commandMenuItemId: v4(),
applicationId: twentyStandardApplicationId,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER,
workspaceId,
position,
now,
@@ -77,6 +77,7 @@ export const createStandardCommandMenuItemFlatMetadata = ({
pageLayoutId: null,
pageLayoutUniversalIdentifier: null,
isActive: true,
isSystemSideEffect: false,
overrides: null,
universalOverrides: null,
createdAt: now,
@@ -9,6 +9,7 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { PARTIAL_SYSTEM_FLAT_FIELD_METADATAS } from 'src/engine/metadata-modules/object-metadata/constants/partial-system-flat-field-metadatas.constant';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
import { type StandardBuilderArgs } from 'src/engine/workspace-manager/twenty-standard-application/types/metadata-standard-buillder-args.type';
@@ -82,6 +83,7 @@ export const createStandardFieldFlatMetadata = <
icon,
isActive: true,
isSystem,
isSystemSideEffect: name in PARTIAL_SYSTEM_FLAT_FIELD_METADATAS,
isNullable,
isUnique,
isUIEditable,
@@ -101,6 +101,7 @@ export const createStandardRelationFieldFlatMetadata = <
icon,
isActive: true,
isSystem: false,
isSystemSideEffect: false,
isNullable,
isUnique: false,
isUIEditable,
@@ -97,6 +97,7 @@ export const createStandardIndexFlatMetadata = <
indexWhereClause,
isCustom: false,
isUnique,
isSystemSideEffect: true,
objectMetadataUniversalIdentifier: flatObjectMetadata.universalIdentifier,
universalIdentifier: indexDefinition.universalIdentifier,
updatedAt: now,
@@ -2,6 +2,7 @@ import { type PageLayoutTabLayoutMode } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util';
@@ -36,6 +37,7 @@ export const createStandardPageLayoutTabFlatMetadata = ({
layoutName as keyof typeof STANDARD_PAGE_LAYOUTS
] as {
universalIdentifier: string;
type: PageLayoutType;
tabs: Record<
string,
StandardPageLayoutTabConfig & {
@@ -69,6 +71,7 @@ export const createStandardPageLayoutTabFlatMetadata = ({
widgetIds,
widgetUniversalIdentifiers,
isActive: true,
isSystemSideEffect: layout.type === PageLayoutType.RECORD_PAGE,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -7,6 +7,7 @@ import { isDefined } from 'twenty-shared/utils';
import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { type WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
import { type AllPageLayoutWidgetConfiguration } from 'src/engine/metadata-modules/page-layout-widget/types/all-page-layout-widget-configuration.type';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
@@ -69,6 +70,7 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
const layout = STANDARD_PAGE_LAYOUTS[
layoutName as keyof typeof STANDARD_PAGE_LAYOUTS
] as {
type: PageLayoutType;
tabs: Record<
string,
StandardPageLayoutTabConfig & {
@@ -107,6 +109,7 @@ export const createStandardPageLayoutWidgetFlatMetadata = ({
objectMetadataId,
objectMetadataUniversalIdentifier,
isActive: true,
isSystemSideEffect: layout.type === PageLayoutType.RECORD_PAGE,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -3,7 +3,7 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
import { type StandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
import { type StandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util';
@@ -119,6 +119,7 @@ export const createStandardPageLayoutFlatMetadata = ({
type,
objectMetadataId,
objectMetadataUniversalIdentifier: objectUniversalIdentifier,
isSystemSideEffect: type === PageLayoutType.RECORD_PAGE,
tabIds: [],
tabUniversalIdentifiers: [],
createdAt: now,
@@ -75,6 +75,8 @@ export type BuildStandardFlatViewFieldMetadataMapsArgs = Omit<
export const buildStandardFlatViewFieldMetadataMaps = (
args: BuildStandardFlatViewFieldMetadataMapsArgs,
): FlatEntityMaps<FlatViewField> => {
const { flatViewMaps } = args.dependencyFlatEntityMaps;
const allViewFieldMetadatas: FlatViewField[] = (
Object.keys(
STANDARD_FLAT_VIEW_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME,
@@ -94,8 +96,16 @@ export const buildStandardFlatViewFieldMetadataMaps = (
let flatViewFieldMaps = createEmptyFlatEntityMaps();
for (const viewFieldMetadata of allViewFieldMetadatas) {
const parentView =
flatViewMaps.byUniversalIdentifier[
viewFieldMetadata.viewUniversalIdentifier
];
flatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: viewFieldMetadata,
flatEntity: {
...viewFieldMetadata,
isSystemSideEffect: parentView?.isSystemSideEffect ?? false,
},
flatEntityMaps: flatViewFieldMaps,
});
}
@@ -121,6 +121,7 @@ export const createStandardViewFieldFlatMetadata = <
size,
aggregateOperation,
isActive: true,
isSystemSideEffect: false,
overrides: null,
universalOverrides: null,
createdAt: now,
@@ -2,8 +2,8 @@ import { isDefined } from 'class-validator';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import {
type AggregateOperations,
type ViewType,
type ViewKey,
ViewType,
ViewKey,
ViewOpenRecordIn,
ViewVisibility,
} from 'twenty-shared/types';
@@ -143,6 +143,8 @@ export const createStandardViewFlatMetadata = <
visibility: ViewVisibility.WORKSPACE,
createdByUserWorkspaceId: null,
isActive: true,
isSystemSideEffect:
key === ViewKey.INDEX || type === ViewType.FIELDS_WIDGET,
overrides: null,
universalOverrides: null,
viewFieldIds: [],
@@ -20,6 +20,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a crea
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
@@ -84,6 +85,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
@@ -162,6 +164,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
@@ -212,6 +215,7 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
@@ -82,6 +82,7 @@ export const fromUniversalFlatIndexToFlatIndex = ({
name: universalFlatIndexMetadata.name,
isCustom: universalFlatIndexMetadata.isCustom,
isUnique: universalFlatIndexMetadata.isUnique,
isSystemSideEffect: universalFlatIndexMetadata.isSystemSideEffect,
indexWhereClause: universalFlatIndexMetadata.indexWhereClause,
indexType: universalFlatIndexMetadata.indexType,
createdAt: universalFlatIndexMetadata.createdAt,
@@ -58,6 +58,7 @@ describe('flatEntityToScalarFlatEntity', () => {
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"label": "Test Label",
@@ -20,6 +20,7 @@ export const getMockFieldMetadataEntity = <
overrides: GetMockFieldMetadataEntityOverride<T>,
): FieldMetadataEntity => {
return {
isSystemSideEffect: false,
workspace: {} as WorkspaceEntity,
calendarViews: [],
mainGroupByFieldMetadataViews: [],
@@ -101,6 +101,90 @@ exports[`successful find view with all sub-relations (e2e) Company View Structur
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 7,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 8,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 9,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 10,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 11,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 12,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
{
"createdAt": Any<String>,
"deletedAt": null,
"fieldMetadataId": Any<String>,
"id": Any<String>,
"isVisible": false,
"position": 13,
"size": 180,
"updatedAt": Any<String>,
"viewFieldGroupId": null,
"viewId": Any<String>,
},
],
"viewFilterGroups": [],
"viewFilters": [],
@@ -69,8 +69,15 @@ describe('successful find view with all sub-relations (e2e)', () => {
jestExpectToBeDefined(testView);
expect(testView).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...testView }),
const stableTestView = {
...testView,
viewFields: [...testView.viewFields].sort(
(a, b) => a.position - b.position,
),
};
expect(stableTestView).toMatchSnapshot(
extractRecordIdsAndDatesAsExpectAny({ ...stableTestView }),
);
});
});
@@ -15,6 +15,7 @@ exports[`syncApplication should create a TEXT field on the standard Company obje
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"label": "Industry",
@@ -108,6 +109,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Id",
@@ -133,6 +135,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Creation date",
@@ -158,6 +161,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Last update",
@@ -183,6 +187,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Deleted at",
@@ -213,6 +218,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Created by",
@@ -243,6 +249,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Updated by",
@@ -268,6 +275,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Position",
@@ -293,6 +301,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Search vector",
@@ -321,6 +330,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"label": "Description",
@@ -388,6 +398,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUIReadOnly": false,
"isUnique": false,
@@ -434,6 +445,7 @@ exports[`syncApplication should delete old field and create equivalent one when
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"label": "Description",
@@ -504,6 +516,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Id",
@@ -529,6 +542,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Creation date",
@@ -554,6 +568,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Last update",
@@ -579,6 +594,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Deleted at",
@@ -609,6 +625,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Created by",
@@ -639,6 +656,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Updated by",
@@ -664,6 +682,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Position",
@@ -689,6 +708,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": true,
"isSystemSideEffect": true,
"isUIEditable": true,
"isUnique": false,
"label": "Search vector",
@@ -717,6 +737,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
"isLabelSyncedWithName": false,
"isNullable": true,
"isSystem": false,
"isSystemSideEffect": false,
"isUIEditable": true,
"isUnique": false,
"label": "Description",
@@ -43,13 +43,30 @@ exports[`createOne FieldMetadataService name/label sync should return an error w
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -44,13 +44,30 @@ exports[`Failing create field metadata tests suite should fail to create NUMERIC
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -110,13 +127,30 @@ exports[`Failing create field metadata tests suite should fail to create POSITIO
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -60,14 +60,31 @@ exports[`createOne FILES field metadata - failing should fail to create files fi
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField, 1 index",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields, 1 index",
"summary": {
"fieldMetadata": 1,
"index": 1,
"totalErrors": 3,
"viewField": 1,
"totalErrors": 4,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -118,13 +135,30 @@ exports[`createOne FILES field metadata - failing should fail to create files fi
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -175,13 +209,30 @@ exports[`createOne FILES field metadata - failing should fail to create files fi
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -232,13 +283,30 @@ exports[`createOne FILES field metadata - failing should fail to create files fi
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -112,14 +112,48 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morph r
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 2 fieldMetadata, 2 viewFields, 2 indices",
"message": "Validation failed for 2 fieldMetadata, 4 viewFields, 2 indices",
"summary": {
"fieldMetadata": 2,
"index": 2,
"totalErrors": 6,
"viewField": 2,
"totalErrors": 8,
"viewField": 4,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -171,13 +205,30 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morph r
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields",
"summary": {
"fieldMetadata": 1,
"totalErrors": 2,
"viewField": 1,
"totalErrors": 3,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -280,14 +331,48 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 2 fieldMetadata, 2 viewFields, 1 index",
"message": "Validation failed for 2 fieldMetadata, 4 viewFields, 1 index",
"summary": {
"fieldMetadata": 2,
"index": 1,
"totalErrors": 5,
"viewField": 2,
"totalErrors": 7,
"viewField": 4,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -397,14 +482,31 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField, 1 index",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields, 1 index",
"summary": {
"fieldMetadata": 1,
"index": 1,
"totalErrors": 3,
"viewField": 1,
"totalErrors": 4,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -474,14 +576,31 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField, 1 index",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields, 1 index",
"summary": {
"fieldMetadata": 1,
"index": 1,
"totalErrors": 3,
"viewField": 1,
"totalErrors": 4,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -551,14 +670,31 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField, 1 index",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields, 1 index",
"summary": {
"fieldMetadata": 1,
"index": 1,
"totalErrors": 3,
"viewField": 1,
"totalErrors": 4,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},
@@ -634,14 +770,31 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"status": "fail",
"type": "create",
},
{
"errors": [
{
"code": "INVALID_VIEW_DATA",
"message": "Field metadata not found",
"userFriendlyMessage": "Field metadata not found",
},
],
"flatEntityMinimalInformation": {
"fieldMetadataUniversalIdentifier": Any<String>,
"universalIdentifier": Any<String>,
"viewUniversalIdentifier": Any<String>,
},
"metadataName": "viewField",
"status": "fail",
"type": "create",
},
],
},
"message": "Validation failed for 1 fieldMetadata, 1 viewField, 1 index",
"message": "Validation failed for 1 fieldMetadata, 2 viewFields, 1 index",
"summary": {
"fieldMetadata": 1,
"index": 1,
"totalErrors": 3,
"viewField": 1,
"totalErrors": 4,
"viewField": 2,
},
"userFriendlyMessage": "Many validation errors",
},

Some files were not shown because too many files have changed in this diff Show More