diff --git a/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx b/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx index ce654a9d12b..57a3b81cf87 100644 --- a/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx +++ b/packages/twenty-docs/developers/extend/apps/data/system-fields.mdx @@ -1,16 +1,22 @@ --- -title: Targeting System Fields -description: Reference auto-created system fields like createdAt or updatedAt from views and other entities with getFieldUniversalIdentifier. +title: Targeting System Metadata +description: Resolve the deterministic universal identifiers of the metadata Twenty provisions automatically on every object, so your app can reference it without hardcoding. icon: "gears" --- -Every object in Twenty ships with a set of **system fields** that you never declare yourself. They are created automatically by the server when the object is provisioned: +Every object in Twenty comes with **system metadata** you never declare yourself, such as a set of fields and a main list view with its columns. The server creates all of it when the object is provisioned, and the set grows as Twenty does. + +Because you don't declare it, there's no `universalIdentifier` constant for you to import. Instead, the server **derives** each identifier deterministically, and `twenty-sdk` exposes the same derivation so your manifest can resolve the exact value the server uses. + +## System fields + +The scalar fields present on every object, none of which you declare with [`defineField()`](/developers/extend/apps/data/extending-objects): `id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector` -Because you don't declare these fields with [`defineField()`](/developers/extend/apps/data/extending-objects), there's no `universalIdentifier` constant for you to import. So how do you reference `createdAt` as a column in a [view](/developers/extend/apps/layout/views)? +So how do you reference `createdAt` as a column in a [view](/developers/extend/apps/layout/views)? -## The problem +### The problem Since Twenty 2.19, a system field's universal identifier is **derived deterministically** by the server from three inputs: the application universal identifier, the object universal identifier, and the field name. Inventing an id and hardcoding it won't work: it matches nothing on the server, and the sync rejects the dangling reference: @@ -18,7 +24,7 @@ Since Twenty 2.19, a system field's universal identifier is **derived determinis Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found ``` -## The solution +### The solution `getFieldUniversalIdentifier` is available from `twenty-sdk` 2.21 onward. @@ -40,7 +46,7 @@ const createdAtFieldId = getFieldUniversalIdentifier({ - `objectUniversalIdentifier` is the identifier of the object the field belongs to. - `name` is the system field name, one of the values listed above. -## Example: a createdAt column in a view +### Example: a createdAt column in a view The typical case is adding a `createdAt` column to a view of one of your custom objects. Resolve the field id and reference it as any other `fieldMetadataUniversalIdentifier`: @@ -86,7 +92,7 @@ The same resolved id works anywhere a `fieldMetadataUniversalIdentifier` is expe inputs change, and avoids drift if the derivation ever evolves. -## System relation fields +### System relation fields `getSystemRelationFieldUniversalIdentifier` is available from `twenty-sdk` @@ -132,18 +138,70 @@ const attachmentTargetRocketFieldId = As with scalar system fields, the resolved id works anywhere a `fieldMetadataUniversalIdentifier` is expected. +## System views + + + `getSystemViewUniversalIdentifier` and `getSystemViewFieldUniversalIdentifier` + are available from `twenty-sdk` 2.26 onward and require a Twenty server on + 2.26 or later. + + +The server also provisions a **system view** on every object: the main list view (`All {objectLabelPlural}`, keyed on `ViewKey.INDEX`), with one column per displayable field. Like system relation fields, their identifiers are derived **name-free**, so renaming an object or a field never changes them. + +Use `getSystemViewUniversalIdentifier` to resolve the view: + +```ts +import { getSystemViewUniversalIdentifier, ViewKey } from 'twenty-sdk/define'; + +const rocketIndexViewId = getSystemViewUniversalIdentifier({ + objectMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: ROCKET_OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, +}); +``` + +- `objectMetadataApplicationUniversalIdentifier` is the application owning the **object**, which is what the view is namespaced by. +- `objectUniversalIdentifier` is the object the view lists. +- `viewKey` is the system view key, `ViewKey.INDEX` today. + +The resolved id works anywhere a `viewUniversalIdentifier` is expected, such as a [`NavigationMenuItemType.VIEW`](/developers/extend/apps/layout/navigation-menu-items) sidebar entry. To simply open an object's main list, prefer `NavigationMenuItemType.OBJECT` with `targetObjectUniversalIdentifier`: it needs no derivation. + +`getSystemViewFieldUniversalIdentifier` resolves a single **column** on a system view, from the view and the field it displays: + +```ts +import { getSystemViewFieldUniversalIdentifier } from 'twenty-sdk/define'; + +const rocketNameColumnId = getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: rocketIndexViewId, + fieldMetadataUniversalIdentifier: ROCKET_NAME_FIELD_UNIVERSAL_IDENTIFIER, +}); +``` + +Note the first argument: a column is namespaced by the application owning the **field it displays**, not the one owning the view. A field your app adds to a standard object gets its column derived under your application, on a view owned by Twenty. + + + System views and their columns are **server-owned**: resolve their identifiers + to reference them, never to declare them. `key` on + [`defineView()`](/developers/extend/apps/layout/views) is deprecated and + ignored, so a manifest view can never claim the `INDEX` key, and the server + already provisions a column for every field you add, so declaring your own + `defineViewField()` for that same field on a system view conflicts with it. + + ## Standard Twenty objects -For a **standard** Twenty object (Person, Company, Opportunity, …), you don't need to derive anything: the system field identifiers are pre-computed constants you can import directly. +For a **standard** Twenty object (Person, Company, Opportunity, …), you don't need to derive anything: the identifiers are pre-computed constants you can import directly, for both fields and views. ```ts import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define'; // STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.createdAt.universalIdentifier // STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.fields.updatedAt.universalIdentifier +// STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople.universalIdentifier ``` -Reach for `getFieldUniversalIdentifier` when the object is one **your app** defines with [`defineObject()`](/developers/extend/apps/data/objects), where no such constant exists. +Reach for the helpers above when the object is one **your app** defines with [`defineObject()`](/developers/extend/apps/data/objects), where no such constant exists. `name` is a **default** field, not a system field. It keeps its own hardcoded diff --git a/packages/twenty-docs/developers/extend/apps/layout/views.mdx b/packages/twenty-docs/developers/extend/apps/layout/views.mdx index 7fe0609737f..8ea606099b7 100644 --- a/packages/twenty-docs/developers/extend/apps/layout/views.mdx +++ b/packages/twenty-docs/developers/extend/apps/layout/views.mdx @@ -35,7 +35,7 @@ export default defineView({ - `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object. - The object's main list view is server-owned: `key` is deprecated and ignored, so a manifest view can never claim it. Ship a `VIEW` navigation item if you want your view one click away in the sidebar. -- `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`. To reference an auto-created system field such as `createdAt`, see [Targeting System Fields](/developers/extend/apps/data/system-fields). +- `fields` controls which columns appear and in what order. Each field references a `fieldMetadataUniversalIdentifier`. To reference an auto-created system field such as `createdAt`, see [Targeting System Metadata](/developers/extend/apps/data/system-fields). - You can also declare `filters`, `filterGroups`, `sorts`, `groups`, and `fieldGroups` for advanced configurations. - `position` controls ordering when multiple views exist for the same object. diff --git a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap index 0c7f2dd5942..ebd1f317ee7 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap +++ b/packages/twenty-sdk/src/cli/utilities/build/common/plugins/__tests__/__snapshots__/stub-twenty-sdk-define.plugin.spec.ts.snap @@ -39,6 +39,8 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1 "featureFlags", "getFieldUniversalIdentifier", "getSystemRelationFieldUniversalIdentifier", + "getSystemViewFieldUniversalIdentifier", + "getSystemViewUniversalIdentifier", "hasAnySoftDeleteFilterOnView", "includes", "includesEvery", diff --git a/packages/twenty-sdk/src/sdk/define/index.ts b/packages/twenty-sdk/src/sdk/define/index.ts index 88be5b61520..d696eada880 100644 --- a/packages/twenty-sdk/src/sdk/define/index.ts +++ b/packages/twenty-sdk/src/sdk/define/index.ts @@ -165,6 +165,10 @@ export { defineSkill } from '@/sdk/define/skills/define-skill'; export { defineView } from '@/sdk/define/views/define-view'; export { defineViewField } from '@/sdk/define/view-fields/define-view-field'; +export { + getSystemViewFieldUniversalIdentifier, + getSystemViewUniversalIdentifier, +} from 'twenty-shared/application'; export type { ViewConfig } from '@/sdk/define/views/view-config'; export { ViewKey } from '@/sdk/define/views/view-key'; export type { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.ts index 2fb9e1e8ebb..70a0bd5b015 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.ts @@ -2,8 +2,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Command } from 'nest-commander'; import { + getSystemViewFieldUniversalIdentifier, getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -34,7 +34,7 @@ type ReownUpdate = { @Command({ name: 'upgrade:2-26:reconcile-index-view-universal-identifier', description: - 'Re-own the INDEX table views ("All {objectLabelPlural}", keyed on ViewKey.INDEX) of the twenty-standard and workspace-custom applications, and all their view fields, onto the engine convention: the view gets the name-free deterministic universal identifier (getSystemViewUniversalIdentifier, object identifier + INDEX key), each view field gets the derived getViewFieldUniversalIdentifier keyed on the application of the field it DISPLAYS — not the row attribution, which diverges when a user shows a hidden standard column and mints a workspace-custom view field on a standard field — so an app or user column on a standard INDEX view converges too, and both get isSystemSideEffect: true, as if provisioned by the metadata side-effect engine. INDEX views of other applications are handled by the demote-and-backfill command. Children reference the view by primary key, so the re-own is a lossless update.', + 'Re-own the INDEX table views ("All {objectLabelPlural}", keyed on ViewKey.INDEX) of the twenty-standard and workspace-custom applications, and all their view fields, onto the engine convention: the view gets the name-free deterministic universal identifier (getSystemViewUniversalIdentifier, object identifier + INDEX key), each view field gets the derived getSystemViewFieldUniversalIdentifier keyed on the application of the field it DISPLAYS — not the row attribution, which diverges when a user shows a hidden standard column and mints a workspace-custom view field on a standard field — so an app or user column on a standard INDEX view converges too, and both get isSystemSideEffect: true, as if provisioned by the metadata side-effect engine. INDEX views of other applications are handled by the demote-and-backfill command. Children reference the view by primary key, so the re-own is a lossless update.', }) export class ReconcileIndexViewUniversalIdentifierCommand extends ProvisionedWorkspaceCommandRunner { constructor( @@ -171,7 +171,7 @@ export class ReconcileIndexViewUniversalIdentifierCommand extends ProvisionedWor } const derivedViewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier: flatObjectMetadata.applicationUniversalIdentifier, objectUniversalIdentifier: flatObjectMetadata.universalIdentifier, viewKey: ViewKey.INDEX, @@ -261,15 +261,14 @@ export class ReconcileIndexViewUniversalIdentifierCommand extends ProvisionedWor return undefined; } - const derivedViewFieldUniversalIdentifier = getViewFieldUniversalIdentifier( - { - applicationUniversalIdentifier: + const derivedViewFieldUniversalIdentifier = + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: flatFieldMetadata.applicationUniversalIdentifier, viewUniversalIdentifier: derivedViewUniversalIdentifier, fieldMetadataUniversalIdentifier: flatViewField.fieldMetadataUniversalIdentifier, - }, - ); + }); const update: ReownUpdate['update'] = {}; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.ts index c5ce6d86aa0..3cd402d9f20 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.ts @@ -2,8 +2,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Command } from 'nest-commander'; import { + getSystemViewFieldUniversalIdentifier, getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -220,7 +220,7 @@ export class DemoteAndBackfillApplicationIndexViewCommand extends ProvisionedWor } const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier: flatObjectMetadata.applicationUniversalIdentifier, objectUniversalIdentifier: flatObjectMetadata.universalIdentifier, viewKey: ViewKey.INDEX, @@ -276,12 +276,14 @@ export class DemoteAndBackfillApplicationIndexViewCommand extends ProvisionedWor const fieldApplicationUniversalIdentifier = flatFieldMetadata.applicationUniversalIdentifier; - const viewFieldUniversalIdentifier = getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: fieldApplicationUniversalIdentifier, - viewUniversalIdentifier: indexViewUniversalIdentifier, - fieldMetadataUniversalIdentifier: - flatFieldMetadata.universalIdentifier, - }); + const viewFieldUniversalIdentifier = + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: + fieldApplicationUniversalIdentifier, + viewUniversalIdentifier: indexViewUniversalIdentifier, + fieldMetadataUniversalIdentifier: + flatFieldMetadata.universalIdentifier, + }); // Already backfilled by a previous (partially failed) run. if ( diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.spec.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.spec.ts index 9a047355e4b..07f5ed428a9 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.spec.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.spec.ts @@ -1,6 +1,6 @@ import { getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, + getSystemViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { ViewKey } from 'twenty-shared/types'; @@ -27,13 +27,13 @@ const FIELD_UNIVERSAL_IDENTIFIER = '20202020-0000-4000-8000-0000000000cc'; const DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); const DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, }); @@ -248,7 +248,7 @@ describe('ReconcileIndexViewUniversalIdentifierCommand', () => { it('re-owns a custom object INDEX view onto the workspace-custom derivation', async () => { const derivedCustomViewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: CUSTOM_OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); @@ -367,8 +367,8 @@ describe('ReconcileIndexViewUniversalIdentifierCommand', () => { // those must converge on the derived scheme so manifest deletion inference // never drops them once the app stops declaring them. const derivedExternalViewFieldUniversalIdentifier = - getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.spec.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.spec.ts index 1ee3df5a0b0..713cabe3924 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.spec.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/__tests__/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command.spec.ts @@ -1,6 +1,6 @@ import { getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, + getSystemViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; import { In } from 'typeorm'; @@ -23,7 +23,7 @@ const OBJECT_UNIVERSAL_IDENTIFIER = '20202020-0000-4000-8000-0000000000bb'; const FIELD_UNIVERSAL_IDENTIFIER = '20202020-0000-4000-8000-0000000000cc'; const DERIVED_VIEW_UNIVERSAL_IDENTIFIER = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); @@ -205,8 +205,8 @@ describe('DemoteAndBackfillApplicationIndexViewCommand', () => { .flatEntityToCreate, ).toEqual([ expect.objectContaining({ - universalIdentifier: getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: + universalIdentifier: getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, @@ -241,8 +241,8 @@ describe('DemoteAndBackfillApplicationIndexViewCommand', () => { ], viewFields: [ { - universalIdentifier: getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: + universalIdentifier: getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, @@ -282,8 +282,8 @@ describe('DemoteAndBackfillApplicationIndexViewCommand', () => { payload.allFlatEntityOperationByMetadataName.viewField.flatEntityToCreate, ).toEqual([ expect.objectContaining({ - universalIdentifier: getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: + universalIdentifier: getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-index-view-field-on-create-side-effect-handler.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-index-view-field-on-create-side-effect-handler.service.spec.ts index 95cb8b0f18b..3faf582ee4a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-index-view-field-on-create-side-effect-handler.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-index-view-field-on-create-side-effect-handler.service.spec.ts @@ -1,7 +1,7 @@ import { getFieldUniversalIdentifier, + getSystemViewFieldUniversalIdentifier, getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; @@ -25,7 +25,8 @@ const PRIORITY_FIELD_UNIVERSAL_IDENTIFIER = const DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: + APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); @@ -37,8 +38,9 @@ const computeViewFieldUniversalIdentifier = ({ viewUniversalIdentifier: string; fieldMetadataUniversalIdentifier: string; }) => - getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: + APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier, fieldMetadataUniversalIdentifier, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service.ts index 837cce885fe..f23c8c1f54c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { + getSystemViewFieldUniversalIdentifier, getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; @@ -56,7 +56,7 @@ export class FieldIndexViewFieldOnCreateSideEffectHandlerService extends Metadat } const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier: parentFlatObjectMetadata.applicationUniversalIdentifier, objectUniversalIdentifier: objectMetadataUniversalIdentifier, viewKey: ViewKey.INDEX, @@ -255,8 +255,9 @@ export class FieldIndexViewFieldOnCreateSideEffectHandlerService extends Metadat const { applicationUniversalIdentifier } = sourceFlatFieldMetadata; return { - universalIdentifier: getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier, + universalIdentifier: getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: + applicationUniversalIdentifier, viewUniversalIdentifier: indexViewUniversalIdentifier, fieldMetadataUniversalIdentifier: sourceFlatFieldMetadata.universalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-index-view-label-identifier-on-update-side-effect-handler.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-index-view-label-identifier-on-update-side-effect-handler.service.spec.ts index 42199407433..cd3e60f93c1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-index-view-label-identifier-on-update-side-effect-handler.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-index-view-label-identifier-on-update-side-effect-handler.service.spec.ts @@ -10,7 +10,8 @@ const NAME_FIELD_UNIVERSAL_IDENTIFIER = 'd1d2d3d4-d5d6-4000-8000-000000000001'; const CODE_FIELD_UNIVERSAL_IDENTIFIER = 'd1d2d3d4-d5d6-4000-8000-000000000002'; const INDEX_VIEW_UNIVERSAL_IDENTIFIER = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: + APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-and-index-view-on-create-side-effect-handler.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-and-index-view-on-create-side-effect-handler.service.spec.ts index 35eece996b3..31316e111ab 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-and-index-view-on-create-side-effect-handler.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-and-index-view-on-create-side-effect-handler.service.spec.ts @@ -1,7 +1,7 @@ import { getFieldUniversalIdentifier, + getSystemViewFieldUniversalIdentifier, getSystemViewUniversalIdentifier, - getViewFieldUniversalIdentifier, } from 'twenty-shared/application'; import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; @@ -46,7 +46,8 @@ const DISPLAYABLE_SYSTEM_FIELD_NAMES = [ const DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataApplicationUniversalIdentifier: + APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, viewKey: ViewKey.INDEX, }); @@ -192,8 +193,9 @@ describe('ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService', () => for (const viewField of viewFields) { expect(viewField.isSystemSideEffect).toBe(true); expect(viewField.universalIdentifier).toBe( - getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: + APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: viewField.fieldMetadataUniversalIdentifier, diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service.ts index 47a54708a2b..23a70ebb2ea 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service.ts @@ -66,7 +66,7 @@ export class ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService exte } const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier: updatedFlatObjectMetadata.applicationUniversalIdentifier, objectUniversalIdentifier: updatedFlatObjectMetadata.universalIdentifier, viewKey: ViewKey.INDEX, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-index-view-to-create.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-index-view-to-create.util.spec.ts index 84f1cd39ab1..80c3446c0e5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-index-view-to-create.util.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-index-view-to-create.util.spec.ts @@ -15,7 +15,8 @@ describe('computeFlatIndexViewToCreate', () => { expect(result.universalIdentifier).toBe( getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier, + objectMetadataApplicationUniversalIdentifier: + applicationUniversalIdentifier, objectUniversalIdentifier, viewKey: ViewKey.INDEX, }), diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util.ts index eb234b914b9..1421eeb635e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util.ts @@ -43,7 +43,8 @@ export const computeFlatIndexViewToCreate = ({ openRecordIn: ViewOpenRecordIn.SIDE_PANEL, position: 0, universalIdentifier: getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier, + objectMetadataApplicationUniversalIdentifier: + applicationUniversalIdentifier, objectUniversalIdentifier: objectMetadata.universalIdentifier, viewKey: ViewKey.INDEX, }), diff --git a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-field-universal-identifier.util.spec.ts b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-field-universal-identifier.util.spec.ts new file mode 100644 index 00000000000..8b1a672d443 --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-field-universal-identifier.util.spec.ts @@ -0,0 +1,34 @@ +import { getSystemViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-field-universal-identifier.util'; +import { getViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-view-field-universal-identifier.util'; + +const APP = '11111111-1111-4111-8111-111111111111'; +const VIEW = '44444444-4444-4444-8444-444444444444'; +const FIELD = '33333333-3333-4333-8333-333333333333'; + +describe('getSystemViewFieldUniversalIdentifier', () => { + it('derives a deterministic id from the field it displays within its view', () => { + expect( + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: APP, + viewUniversalIdentifier: VIEW, + fieldMetadataUniversalIdentifier: FIELD, + }), + ).toBe('fe533473-ef93-5ed9-b709-b344aa94fb8d'); + }); + + it('derives the same id as getViewFieldUniversalIdentifier', () => { + expect( + getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: APP, + viewUniversalIdentifier: VIEW, + fieldMetadataUniversalIdentifier: FIELD, + }), + ).toBe( + getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: APP, + viewUniversalIdentifier: VIEW, + fieldMetadataUniversalIdentifier: FIELD, + }), + ); + }); +}); diff --git a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-universal-identifier.util.spec.ts b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-universal-identifier.util.spec.ts index a740c2c2f40..3930f3ca881 100644 --- a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-universal-identifier.util.spec.ts +++ b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-universal-identifier.util.spec.ts @@ -8,7 +8,7 @@ describe('getSystemViewUniversalIdentifier', () => { it('derives a deterministic id from the stable INDEX view key within its object', () => { expect( getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: APP, + objectMetadataApplicationUniversalIdentifier: APP, objectUniversalIdentifier: OBJECT, viewKey: ViewKey.INDEX, }), diff --git a/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-field-universal-identifier.util.ts b/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-field-universal-identifier.util.ts new file mode 100644 index 00000000000..5bf7763bce3 --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-field-universal-identifier.util.ts @@ -0,0 +1,16 @@ +import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util'; + +export const getSystemViewFieldUniversalIdentifier = ({ + fieldMetadataApplicationUniversalIdentifier, + viewUniversalIdentifier, + fieldMetadataUniversalIdentifier, +}: { + fieldMetadataApplicationUniversalIdentifier: string; + viewUniversalIdentifier: string; + fieldMetadataUniversalIdentifier: string; +}): string => + computeDeterministicUuid({ + entityNamespace: 'viewField', + value: `${viewUniversalIdentifier}:${fieldMetadataUniversalIdentifier}`, + applicationUniversalIdentifier: fieldMetadataApplicationUniversalIdentifier, + }); diff --git a/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-universal-identifier.util.ts b/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-universal-identifier.util.ts index 51b88da6664..1f18f57789c 100644 --- a/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-universal-identifier.util.ts +++ b/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-universal-identifier.util.ts @@ -2,16 +2,17 @@ import { computeDeterministicUuid } from '@/application/deterministic-identifier import { type ViewKey } from '@/types/ViewKey'; export const getSystemViewUniversalIdentifier = ({ - applicationUniversalIdentifier, + objectMetadataApplicationUniversalIdentifier, objectUniversalIdentifier, viewKey, }: { - applicationUniversalIdentifier: string; + objectMetadataApplicationUniversalIdentifier: string; objectUniversalIdentifier: string; viewKey: ViewKey; }): string => computeDeterministicUuid({ entityNamespace: 'view', value: `${objectUniversalIdentifier}:${viewKey}`, - applicationUniversalIdentifier, + applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier, }); diff --git a/packages/twenty-shared/src/application/index.ts b/packages/twenty-shared/src/application/index.ts index 245432fc625..613c04218a7 100644 --- a/packages/twenty-shared/src/application/index.ts +++ b/packages/twenty-shared/src/application/index.ts @@ -80,6 +80,7 @@ export { getSearchFieldUniversalIdentifier } from './deterministic-identifier/ge export { getSelectOptionUniversalIdentifier } from './deterministic-identifier/get-select-option-universal-identifier.util'; export { getSkillUniversalIdentifier } from './deterministic-identifier/get-skill-universal-identifier.util'; export { getSystemRelationFieldUniversalIdentifier } from './deterministic-identifier/get-system-relation-field-universal-identifier.util'; +export { getSystemViewFieldUniversalIdentifier } from './deterministic-identifier/get-system-view-field-universal-identifier.util'; export { getSystemViewUniversalIdentifier } from './deterministic-identifier/get-system-view-universal-identifier.util'; export { getViewFieldGroupUniversalIdentifier } from './deterministic-identifier/get-view-field-group-universal-identifier.util'; export { getViewFieldUniversalIdentifier } from './deterministic-identifier/get-view-field-universal-identifier.util'; diff --git a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts index 90d9f8063d2..00908600d2b 100644 --- a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts +++ b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts @@ -12,7 +12,7 @@ import { buildStandardObjectIndexView } from '@/metadata/utils/internal/build-st // keyed on ViewKey.INDEX) and their view-field universal identifiers are // deterministically derived by buildStandardObjectIndexView // (getSystemViewUniversalIdentifier for the view, -// getViewFieldUniversalIdentifier for each view field). +// getSystemViewFieldUniversalIdentifier for each view field). export const STANDARD_OBJECTS = { attachment: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, diff --git a/packages/twenty-shared/src/metadata/utils/internal/build-standard-object-index-view.util.ts b/packages/twenty-shared/src/metadata/utils/internal/build-standard-object-index-view.util.ts index 4fb6e3a1e00..04e3db24639 100644 --- a/packages/twenty-shared/src/metadata/utils/internal/build-standard-object-index-view.util.ts +++ b/packages/twenty-shared/src/metadata/utils/internal/build-standard-object-index-view.util.ts @@ -1,6 +1,6 @@ import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from '@/application/constants/TwentyStandardApplicationUniversalIdentifier'; +import { getSystemViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-field-universal-identifier.util'; import { getSystemViewUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-universal-identifier.util'; -import { getViewFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-view-field-universal-identifier.util'; import { ViewKey } from '@/types/ViewKey'; type StandardViewFieldUniversalIdentifier = { universalIdentifier: string }; @@ -20,7 +20,7 @@ export const buildStandardObjectIndexView = < viewFields: Record; } => { const viewUniversalIdentifier = getSystemViewUniversalIdentifier({ - applicationUniversalIdentifier: + objectMetadataApplicationUniversalIdentifier: TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, objectUniversalIdentifier, viewKey: ViewKey.INDEX, @@ -39,8 +39,8 @@ export const buildStandardObjectIndexView = < return [ viewFieldName, { - universalIdentifier: getViewFieldUniversalIdentifier({ - applicationUniversalIdentifier: + universalIdentifier: getSystemViewFieldUniversalIdentifier({ + fieldMetadataApplicationUniversalIdentifier: TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, viewUniversalIdentifier, fieldMetadataUniversalIdentifier: field.universalIdentifier,