diff --git a/packages/twenty-apps/examples/document-generator/src/views/documents.view.ts b/packages/twenty-apps/examples/document-generator/src/views/documents.view.ts index 260d3fc532..2b97500ee0 100644 --- a/packages/twenty-apps/examples/document-generator/src/views/documents.view.ts +++ b/packages/twenty-apps/examples/document-generator/src/views/documents.view.ts @@ -1,4 +1,4 @@ -import { defineView, ViewKey } from 'twenty-sdk/define'; +import { defineView } from 'twenty-sdk/define'; import { DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER, @@ -16,7 +16,6 @@ export default defineView({ name: 'All documents', objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER, icon: 'IconFile', - key: ViewKey.INDEX, position: 0, fields: [ { diff --git a/packages/twenty-apps/examples/document-generator/src/views/templates.view.ts b/packages/twenty-apps/examples/document-generator/src/views/templates.view.ts index d32feca6d1..56453947bb 100644 --- a/packages/twenty-apps/examples/document-generator/src/views/templates.view.ts +++ b/packages/twenty-apps/examples/document-generator/src/views/templates.view.ts @@ -1,4 +1,4 @@ -import { defineView, ViewKey } from 'twenty-sdk/define'; +import { defineView } from 'twenty-sdk/define'; import { DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER, @@ -14,7 +14,6 @@ export default defineView({ name: 'All templates', objectUniversalIdentifier: DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER, icon: 'IconFileText', - key: ViewKey.INDEX, position: 0, fields: [ { diff --git a/packages/twenty-apps/examples/hello-world/src/views/example-view.ts b/packages/twenty-apps/examples/hello-world/src/views/example-view.ts index 113a610faf..eaaaf9a326 100644 --- a/packages/twenty-apps/examples/hello-world/src/views/example-view.ts +++ b/packages/twenty-apps/examples/hello-world/src/views/example-view.ts @@ -1,4 +1,4 @@ -import { defineView, ViewKey } from 'twenty-sdk/define'; +import { defineView } from 'twenty-sdk/define'; import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'; export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '965e3776-b966-4be8-83f7-6cd3bce5e1bd'; @@ -8,7 +8,6 @@ export default defineView({ name: 'All example items', objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, icon: 'IconList', - key: ViewKey.INDEX, position: 0, fields: [ { diff --git a/packages/twenty-apps/public/last-contact/CHANGELOG.md b/packages/twenty-apps/public/last-contact/CHANGELOG.md index 12796d27a8..bbfa86a893 100644 --- a/packages/twenty-apps/public/last-contact/CHANGELOG.md +++ b/packages/twenty-apps/public/last-contact/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.3 + +- Stop declaring INDEX view fields explicitly: the server now provisions the INDEX view column for each app field automatically, so the manifest no longer targets the engine-owned standard INDEX views. + ## 1.1.1 - Throttle backfill updates and retry rate-limited or transient API failures with exponential backoff, so install/upgrade no longer fails behind Cloudflare rate limiting. diff --git a/packages/twenty-apps/public/last-contact/package.json b/packages/twenty-apps/public/last-contact/package.json index a5f886dbd3..e81bd57c14 100644 --- a/packages/twenty-apps/public/last-contact/package.json +++ b/packages/twenty-apps/public/last-contact/package.json @@ -1,6 +1,6 @@ { "name": "@twentyhq/last-contact", - "version": "1.1.2", + "version": "1.1.3", "license": "MIT", "engines": { "node": "^24.5.0", diff --git a/packages/twenty-apps/public/last-contact/src/constants/universal-identifiers.ts b/packages/twenty-apps/public/last-contact/src/constants/universal-identifiers.ts index b688911411..3c87d5619e 100644 --- a/packages/twenty-apps/public/last-contact/src/constants/universal-identifiers.ts +++ b/packages/twenty-apps/public/last-contact/src/constants/universal-identifiers.ts @@ -5,8 +5,6 @@ export const APPLICATION_UNIVERSAL_IDENTIFIER = '66a504cc-0a75-410e-a43f-cdeae1d export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = '34187abe-1b98-4153-85cd-4808e0aebf30'; export const LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER = '4e0fd7ff-0bbc-47b2-baab-5fe2c0d12557'; -export const LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '8fc4d931-426c-413b-bd7e-0c10d1ad8d0a'; export const EMAIL_INTERACTION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = '87c8926b-15e3-43ed-a303-60f6d072f351'; export const CALENDAR_INTERACTION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = @@ -20,8 +18,6 @@ export const LAST_CONTACT_BY_FIELD_UNIVERSAL_IDENTIFIER = 'cfdee7bd-8d41-41e6-a888-512705e75d7b'; export const LAST_CONTACT_FOR_PEOPLE_ON_WORKSPACE_MEMBER_FIELD_UNIVERSAL_IDENTIFIER = 'bec41e57-6b6f-4bb2-90cc-4030384dd8f6'; -export const LAST_CONTACT_BY_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - 'd68d71f7-10f3-42a2-ab81-a709eee83ca7'; export const LAST_CONTACT_ITEM_MORPH_ID = '97eafe97-0ee1-4857-b233-fec855f6bf1a'; @@ -33,10 +29,6 @@ export const LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER = 'cceb21fe-1422-40d4-bc8b-44c12c79c452'; export const LAST_CONTACT_FOR_PEOPLE_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER = 'f49ce9ca-5aab-445d-9749-db256aaf74b7'; -export const LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '88ab5daf-0650-4813-b9ca-f413d7c9f611'; -export const LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '9c264385-e833-4d88-8149-e392651eb5dd'; export const LAST_OUTBOUND_AT_FIELD_UNIVERSAL_IDENTIFIER = 'f4bbac22-8a3d-44c2-bbf2-7e28237252e1'; @@ -63,12 +55,6 @@ export const LAST_CONTACT_FOR_COMPANIES_ON_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER = '9f08c2e0-23ba-413c-94c8-04ba828586a3'; export const LAST_CONTACT_FOR_COMPANIES_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER = '79103905-cfdb-413a-b964-8f347332ef3c'; -export const COMPANY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '08244258-dc60-475f-a2c7-3122f8b658fb'; -export const COMPANY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '7adc5b23-1a39-4bb6-8cff-ac4e979d38f3'; -export const COMPANY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '61a3b683-3129-4265-889e-309d7ec57403'; export const OPPORTUNITY_LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER = '69225d61-0efb-4959-af6d-566caba58412'; @@ -82,9 +68,3 @@ export const LAST_CONTACT_FOR_OPPORTUNITIES_ON_MESSAGE_FIELD_UNIVERSAL_IDENTIFIE '1ccff5c2-44c3-4a7d-847f-c74440fe56a9'; export const LAST_CONTACT_FOR_OPPORTUNITIES_ON_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER = 'db36a44e-a6dc-4bec-a23e-3b27f63d1629'; -export const OPPORTUNITY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - '8b447030-5124-4cfa-bd96-9836c25d4fc9'; -export const OPPORTUNITY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - 'cac2ec01-6c8b-4d50-b897-7717b2679842'; -export const OPPORTUNITY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = - 'ab720bee-a0f3-41b9-9c94-8beb50e0b525'; diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-at.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-at.view-field.ts deleted file mode 100644 index e4046184f6..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-at.view-field.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - COMPANY_LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - COMPANY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: COMPANY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.views.allCompanies - .universalIdentifier, - fieldMetadataUniversalIdentifier: - COMPANY_LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 8, - isVisible: true, - size: 150, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-calendar-event.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-calendar-event.view-field.ts deleted file mode 100644 index add53b477f..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-calendar-event.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - COMPANY_LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - COMPANY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - COMPANY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.views.allCompanies - .universalIdentifier, - fieldMetadataUniversalIdentifier: - COMPANY_LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - position: 10, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-message.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-message.view-field.ts deleted file mode 100644 index 0c32c366b7..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/company-last-contact-item-message.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - COMPANY_LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - COMPANY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - COMPANY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.views.allCompanies - .universalIdentifier, - fieldMetadataUniversalIdentifier: - COMPANY_LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - position: 9, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-at.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-at.view-field.ts deleted file mode 100644 index c678085b03..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-at.view-field.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople - .universalIdentifier, - fieldMetadataUniversalIdentifier: LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 8, - isVisible: true, - size: 150, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-by.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-by.view-field.ts deleted file mode 100644 index 61efb28327..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-by.view-field.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - LAST_CONTACT_BY_FIELD_UNIVERSAL_IDENTIFIER, - LAST_CONTACT_BY_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: LAST_CONTACT_BY_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople - .universalIdentifier, - fieldMetadataUniversalIdentifier: LAST_CONTACT_BY_FIELD_UNIVERSAL_IDENTIFIER, - position: 7, - isVisible: true, - size: 150, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-calendar-event.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-calendar-event.view-field.ts deleted file mode 100644 index f33c52fccc..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-calendar-event.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople - .universalIdentifier, - fieldMetadataUniversalIdentifier: - LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - position: 10, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-message.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-message.view-field.ts deleted file mode 100644 index 41866cc21b..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/last-contact-item-message.view-field.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.views.allPeople - .universalIdentifier, - fieldMetadataUniversalIdentifier: - LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - position: 9, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-at.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-at.view-field.ts deleted file mode 100644 index 2fd2586628..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-at.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - OPPORTUNITY_LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - OPPORTUNITY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - OPPORTUNITY_LAST_CONTACT_AT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.views.allOpportunities - .universalIdentifier, - fieldMetadataUniversalIdentifier: - OPPORTUNITY_LAST_CONTACT_AT_FIELD_UNIVERSAL_IDENTIFIER, - position: 7, - isVisible: true, - size: 150, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-calendar-event.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-calendar-event.view-field.ts deleted file mode 100644 index b2a8b1069e..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-calendar-event.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - OPPORTUNITY_LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - OPPORTUNITY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - OPPORTUNITY_LAST_CONTACT_ITEM_CALENDAR_EVENT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.views.allOpportunities - .universalIdentifier, - fieldMetadataUniversalIdentifier: - OPPORTUNITY_LAST_CONTACT_ITEM_CALENDAR_EVENT_FIELD_UNIVERSAL_IDENTIFIER, - position: 9, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-message.view-field.ts b/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-message.view-field.ts deleted file mode 100644 index c33dc85979..0000000000 --- a/packages/twenty-apps/public/last-contact/src/view-fields/opportunity-last-contact-item-message.view-field.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - defineViewField, - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, -} from 'twenty-sdk/define'; - -import { - OPPORTUNITY_LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - OPPORTUNITY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, -} from 'src/constants/universal-identifiers'; - -export default defineViewField({ - universalIdentifier: - OPPORTUNITY_LAST_CONTACT_ITEM_MESSAGE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, - viewUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.views.allOpportunities - .universalIdentifier, - fieldMetadataUniversalIdentifier: - OPPORTUNITY_LAST_CONTACT_ITEM_MESSAGE_FIELD_UNIVERSAL_IDENTIFIER, - position: 8, - isVisible: true, - size: 180, -}); diff --git a/packages/twenty-docs/developers/extend/apps/layout/views.mdx b/packages/twenty-docs/developers/extend/apps/layout/views.mdx index 45a22255e5..7fe0609737 100644 --- a/packages/twenty-docs/developers/extend/apps/layout/views.mdx +++ b/packages/twenty-docs/developers/extend/apps/layout/views.mdx @@ -4,10 +4,12 @@ description: Ship pre-configured saved views — column order, filters, groups icon: 'list' --- -A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app — typically a default index view for each custom object you create. +A **view** is a saved configuration for how records of an object are displayed: which fields appear, their order, whether they're visible, and any filters or groups applied. Use `defineView()` to ship pre-configured views with your app. + +Every object automatically gets its main list view (the "All ..." table an `OBJECT` navigation item opens) provisioned by the server — including the objects your app creates, with one column per field your app declares. Views you define with `defineView()` are always **additional** views on top of that default one. ```ts src/views/example-view.ts -import { defineView, ViewKey } from 'twenty-sdk/define'; +import { defineView } from 'twenty-sdk/define'; import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; import { NAME_FIELD_UNIVERSAL_IDENTIFIER } from '../objects/example-object'; @@ -16,7 +18,6 @@ export default defineView({ name: 'All example items', objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, icon: 'IconList', - key: ViewKey.INDEX, position: 0, fields: [ { @@ -33,7 +34,7 @@ export default defineView({ ## Key points - `objectUniversalIdentifier` specifies which object this view applies to. It can be a custom object you defined or a standard Twenty object. -- `key: ViewKey.INDEX` marks the view as the object's main list view (the one an `OBJECT` navigation item opens). +- 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). - 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-docs/developers/extend/apps/tutorials/document-generator/building-the-ui.mdx b/packages/twenty-docs/developers/extend/apps/tutorials/document-generator/building-the-ui.mdx index 340e0690ab..31e5c3ad1d 100644 --- a/packages/twenty-docs/developers/extend/apps/tutorials/document-generator/building-the-ui.mdx +++ b/packages/twenty-docs/developers/extend/apps/tutorials/document-generator/building-the-ui.mdx @@ -13,17 +13,18 @@ document, and a native rich-text **editor** tab for templates. A [view](/developers/extend/apps/layout/views) is a saved list of a given object. A [navigation menu item](/developers/extend/apps/layout/navigation-menu-items) -puts that view in the sidebar. +puts that view in the sidebar. The server already provisions each object's +default "All ..." view automatically, so the views an app defines are +additional, curated ones. ```ts filename="src/views/documents.view.ts" -import { defineView, ViewKey } from 'twenty-sdk/define'; +import { defineView } from 'twenty-sdk/define'; export default defineView({ universalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER, name: 'All documents', objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER, icon: 'IconFile', - key: ViewKey.INDEX, position: 0, fields: [ { universalIdentifier: DOCUMENTS_VIEW_NAME_FIELD_UNIVERSAL_IDENTIFIER, diff --git a/packages/twenty-sdk/src/sdk/define/view-fields/__tests__/define-view-field.spec.ts b/packages/twenty-sdk/src/sdk/define/view-fields/__tests__/define-view-field.spec.ts index 2c9ed516bc..776d96b000 100644 --- a/packages/twenty-sdk/src/sdk/define/view-fields/__tests__/define-view-field.spec.ts +++ b/packages/twenty-sdk/src/sdk/define/view-fields/__tests__/define-view-field.spec.ts @@ -3,7 +3,7 @@ import { type StandaloneViewFieldManifest } from 'twenty-shared/application'; const validConfig: StandaloneViewFieldManifest = { universalIdentifier: '40b17076-ea50-4e42-968e-8989a95f2b5d', - viewUniversalIdentifier: '20202020-a002-4a02-8a02-ae0a1ea11a00', + viewUniversalIdentifier: '0cda0ad4-74da-5ccd-975c-a0e4db43a05a', fieldMetadataUniversalIdentifier: '4e0fd7ff-0bbc-47b2-baab-5fe2c0d12557', position: 10, isVisible: true, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts index 7311f83cf1..1b3a717aa4 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-23/1-23-workspace-command-1780000001500-backfill-record-page-layouts.command.ts @@ -522,7 +522,8 @@ export class BackfillRecordPageLayoutsCommand extends ProvisionedWorkspaceComman const viewFields = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: objectFieldMetadatas, viewUniversalIdentifier: fieldsView.universalIdentifier, - flatApplication: twentyStandardFlatApplication, + applicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: customObject.labelIdentifierFieldMetadataUniversalIdentifier, excludeLabelIdentifier: true, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts index 8f9481b387..c115a4b756 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-25/2-25-workspace-command-1785229970000-add-message-campaign-name-field.command.ts @@ -1,6 +1,7 @@ import { Command } from 'nest-commander'; import { getSearchFieldUniversalIdentifier } from 'twenty-shared/application'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; +import { ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner'; @@ -13,8 +14,8 @@ import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { type FlatSearchFieldMetadata } from 'src/engine/metadata-modules/flat-search-field-metadata/types/flat-search-field-metadata.type'; -import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; +import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; @@ -159,15 +160,34 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm ); } - viewFieldsToCreate.push({ - ...standardNameViewField, - position: this.computeNameViewFieldPosition({ - flatViewMaps, - flatViewFieldMaps, - standardNameViewField, - isNameLabelIdentifier, - }), - }); + const campaignIndexFlatView = + campaignObjectMetadata.viewUniversalIdentifiers + .map( + (viewUniversalIdentifier) => + flatViewMaps.byUniversalIdentifier[viewUniversalIdentifier], + ) + .filter(isDefined) + .find( + (flatView) => + flatView.key === ViewKey.INDEX && !isDefined(flatView.deletedAt), + ); + + if (isDefined(campaignIndexFlatView)) { + viewFieldsToCreate.push({ + ...standardNameViewField, + viewUniversalIdentifier: campaignIndexFlatView.universalIdentifier, + position: this.computeNameViewFieldPosition({ + campaignIndexFlatView, + flatViewFieldMaps, + standardNameViewField, + isNameLabelIdentifier, + }), + }); + } else { + this.logger.warn( + `No INDEX view found for messageCampaign in workspace ${workspaceId}, skipping the name view column`, + ); + } } const searchFieldMetadatasToCreate = @@ -255,32 +275,26 @@ export class AddMessageCampaignNameFieldCommand extends ProvisionedWorkspaceComm // it), above all of them otherwise (the validator forbids anything below the // label identifier). private computeNameViewFieldPosition({ - flatViewMaps, + campaignIndexFlatView, flatViewFieldMaps, standardNameViewField, isNameLabelIdentifier, }: { - flatViewMaps: FlatEntityMaps; + campaignIndexFlatView: FlatView; flatViewFieldMaps: FlatEntityMaps; standardNameViewField: FlatViewField; isNameLabelIdentifier: boolean; }): number { - const flatView = findFlatEntityByUniversalIdentifier({ - flatEntityMaps: flatViewMaps, - universalIdentifier: standardNameViewField.viewUniversalIdentifier, - }); - - if (!isDefined(flatView)) { - return standardNameViewField.position; - } - - const otherViewFieldPositions = flatView.viewFieldUniversalIdentifiers - .map( - (viewFieldUniversalIdentifier) => - flatViewFieldMaps.byUniversalIdentifier[viewFieldUniversalIdentifier], - ) - .filter(isDefined) - .map(({ position }) => position); + const otherViewFieldPositions = + campaignIndexFlatView.viewFieldUniversalIdentifiers + .map( + (viewFieldUniversalIdentifier) => + flatViewFieldMaps.byUniversalIdentifier[ + viewFieldUniversalIdentifier + ], + ) + .filter(isDefined) + .map(({ position }) => position); if (otherViewFieldPositions.length === 0) { return standardNameViewField.position; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-upgrade-version-command.module.ts new file mode 100644 index 0000000000..1ef850f490 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-upgrade-version-command.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; +import { ReconcileIndexViewUniversalIdentifierCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command'; +import { DemoteAndBackfillApplicationIndexViewCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module'; +import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ViewEntity]), + ApplicationModule, + WorkspaceCacheModule, + WorkspaceMigrationModule, + WorkspaceMigrationRunnerModule, + WorkspaceIteratorModule, + ], + providers: [ + ReconcileIndexViewUniversalIdentifierCommand, + DemoteAndBackfillApplicationIndexViewCommand, + ], +}) +export class V2_26_UpgradeVersionCommandModule {} 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 new file mode 100644 index 0000000000..2fb9e1e8eb --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command.ts @@ -0,0 +1,291 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; + +import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner'; +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { invalidateIndexViewReconcileCache } from 'src/database/commands/upgrade-version-command/2-26/utils/invalidate-index-view-reconcile-cache.util'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps.util'; +import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +type ReownUpdate = { + id: string; + update: { + universalIdentifier?: string; + isSystemSideEffect?: boolean; + }; +}; + +@RegisteredWorkspaceCommand('2.26.0', 1785255689000) +@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.', +}) +export class ReconcileIndexViewUniversalIdentifierCommand extends ProvisionedWorkspaceCommandRunner { + constructor( + protected readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly applicationService: ApplicationService, + // eslint-disable-next-line twenty/prefer-workspace-scoped-repository + @InjectRepository(ViewEntity) + private readonly viewRepository: Repository, + ) { + super(workspaceIteratorService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + const { + flatViewMaps, + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatViewMaps', + 'flatViewFieldMaps', + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const { twentyStandardFlatApplication, workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + const engineOwnedApplicationUniversalIdentifiers = new Set([ + twentyStandardFlatApplication.universalIdentifier, + workspaceCustomFlatApplication.universalIdentifier, + ]); + + const flatIndexViews = Object.values(flatViewMaps.byUniversalIdentifier) + .filter(isDefined) + .filter( + (flatView) => + flatView.key === ViewKey.INDEX && + !isDefined(flatView.deletedAt) && + engineOwnedApplicationUniversalIdentifiers.has( + flatView.applicationUniversalIdentifier, + ), + ); + + const { viewUpdates, viewFieldUpdates } = this.computeReownUpdates({ + workspaceId, + flatIndexViews, + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }); + + if (viewUpdates.length === 0 && viewFieldUpdates.length === 0) { + this.logger.log( + `No INDEX view to reconcile for workspace ${workspaceId}`, + ); + + return; + } + + this.logger.log( + `${isDryRun ? '[DRY RUN] ' : ''}Reconciling ${viewUpdates.length} INDEX view(s) and ${viewFieldUpdates.length} view field(s) for workspace ${workspaceId}`, + ); + + if (isDryRun) { + return; + } + + await this.viewRepository.manager.transaction(async (entityManager) => { + const transactionalViewRepository = + entityManager.getRepository(ViewEntity); + const transactionalViewFieldRepository = + entityManager.getRepository(ViewFieldEntity); + + for (const { id, update } of viewUpdates) { + await transactionalViewRepository.update({ id, workspaceId }, update); + } + + for (const { id, update } of viewFieldUpdates) { + await transactionalViewFieldRepository.update( + { id, workspaceId }, + update, + ); + } + }); + + await invalidateIndexViewReconcileCache({ + workspaceId, + workspaceMigrationRunnerService: this.workspaceMigrationRunnerService, + }); + + this.logger.log( + `Reconciled ${viewUpdates.length} INDEX view(s) and ${viewFieldUpdates.length} view field(s) for workspace ${workspaceId}`, + ); + } + + private computeReownUpdates({ + workspaceId, + flatIndexViews, + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + }: { + workspaceId: string; + flatIndexViews: NonNullable< + AllFlatEntityMaps['flatViewMaps']['byUniversalIdentifier'][string] + >[]; + flatViewFieldMaps: AllFlatEntityMaps['flatViewFieldMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; + flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; + }): { viewUpdates: ReownUpdate[]; viewFieldUpdates: ReownUpdate[] } { + const viewUpdates: ReownUpdate[] = []; + const viewFieldUpdates: ReownUpdate[] = []; + + for (const flatView of flatIndexViews) { + const flatObjectMetadata = + flatObjectMetadataMaps.byUniversalIdentifier[ + flatView.objectMetadataUniversalIdentifier + ]; + + if (!isDefined(flatObjectMetadata)) { + this.logger.warn( + `Missing object for INDEX view ${flatView.id} in workspace ${workspaceId}, skipping`, + ); + continue; + } + + const derivedViewUniversalIdentifier = getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: + flatObjectMetadata.applicationUniversalIdentifier, + objectUniversalIdentifier: flatObjectMetadata.universalIdentifier, + viewKey: ViewKey.INDEX, + }); + + const viewUpdate = this.computeViewReownUpdate({ + flatView, + derivedViewUniversalIdentifier, + }); + + if (isDefined(viewUpdate)) { + viewUpdates.push(viewUpdate); + } + + viewFieldUpdates.push( + ...findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMaps({ + flatEntityMaps: flatViewFieldMaps, + universalIdentifiers: flatView.viewFieldUniversalIdentifiers, + }) + .map((flatViewField) => + this.computeViewFieldReownUpdate({ + workspaceId, + flatViewField, + derivedViewUniversalIdentifier, + flatFieldMetadataMaps, + }), + ) + .filter(isDefined), + ); + } + + return { viewUpdates, viewFieldUpdates }; + } + + private computeViewReownUpdate({ + flatView, + derivedViewUniversalIdentifier, + }: { + flatView: NonNullable< + AllFlatEntityMaps['flatViewMaps']['byUniversalIdentifier'][string] + >; + derivedViewUniversalIdentifier: string; + }): ReownUpdate | undefined { + const update: ReownUpdate['update'] = {}; + + if (flatView.universalIdentifier !== derivedViewUniversalIdentifier) { + update.universalIdentifier = derivedViewUniversalIdentifier; + } + if (!flatView.isSystemSideEffect) { + update.isSystemSideEffect = true; + } + + if (Object.keys(update).length === 0) { + return undefined; + } + + return { id: flatView.id, update }; + } + + private computeViewFieldReownUpdate({ + workspaceId, + flatViewField, + derivedViewUniversalIdentifier, + flatFieldMetadataMaps, + }: { + workspaceId: string; + flatViewField: NonNullable< + AllFlatEntityMaps['flatViewFieldMaps']['byUniversalIdentifier'][string] + >; + derivedViewUniversalIdentifier: string; + flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; + }): ReownUpdate | undefined { + if (isDefined(flatViewField.deletedAt)) { + return undefined; + } + + const flatFieldMetadata = + flatFieldMetadataMaps.byUniversalIdentifier[ + flatViewField.fieldMetadataUniversalIdentifier + ]; + + if (!isDefined(flatFieldMetadata)) { + this.logger.warn( + `Missing field for INDEX view field ${flatViewField.id} in workspace ${workspaceId}, skipping`, + ); + + return undefined; + } + + const derivedViewFieldUniversalIdentifier = getViewFieldUniversalIdentifier( + { + applicationUniversalIdentifier: + flatFieldMetadata.applicationUniversalIdentifier, + viewUniversalIdentifier: derivedViewUniversalIdentifier, + fieldMetadataUniversalIdentifier: + flatViewField.fieldMetadataUniversalIdentifier, + }, + ); + + const update: ReownUpdate['update'] = {}; + + if ( + flatViewField.universalIdentifier !== derivedViewFieldUniversalIdentifier + ) { + update.universalIdentifier = derivedViewFieldUniversalIdentifier; + } + if (!flatViewField.isSystemSideEffect) { + update.isSystemSideEffect = true; + } + + if (Object.keys(update).length === 0) { + return undefined; + } + + return { id: flatViewField.id, 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 new file mode 100644 index 0000000000..c5ce6d86aa --- /dev/null +++ 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 @@ -0,0 +1,411 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { In, Repository } from 'typeorm'; + +import { ProvisionedWorkspaceCommandRunner } from 'src/database/commands/command-runners/provisioned-workspace.command-runner'; +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { invalidateIndexViewReconcileCache } from 'src/database/commands/upgrade-version-command/2-26/utils/invalidate-index-view-reconcile-cache.util'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant'; +import { computeFlatIndexViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util'; +import { isFlatFieldMetadataDisplayableInDefaultView } from 'src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util'; +import { orderFlatFieldMetadatasForSystemIndexView } from 'src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type'; +import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +type BackfillOperationsByApplication = Map< + string, + { + viewsToCreate: UniversalFlatView[]; + viewFieldsToCreate: UniversalFlatViewField[]; + } +>; + +@RegisteredWorkspaceCommand('2.26.0', 1785255690000) +@Command({ + name: 'upgrade:2-26:demote-and-backfill-application-index-view', + description: + 'Manifest-installed applications never had their INDEX view auto-provisioned: the ones that declared a view with ViewKey.INDEX authored it themselves under a manifest identifier. The engine is now the sole owner of the INDEX key, so every caller-authored (isSystemSideEffect: false) INDEX view owned by an application other than twenty-standard and workspace-custom is demoted to key: null — it becomes a plain additional view under its manifest identifier, and the flat view validator rejects caller-provided INDEX keys so a later app sync cannot promote it back. Every application object left without an engine-owned INDEX view then gets one backfilled through the workspace migration pipeline with its full view-field layout, converging upgraded installs with fresh installs. Views are committed before view fields across all applications because a view field belongs to the application owning its field, which can differ from the application owning the object (fields can be created on another application object). The backfill runs through the legacy pipeline path (no side-effect expansion: it replays a state the engine convention already defines) and is idempotent and retry-safe: engine-owned INDEX views are neither demoted nor re-backfilled, and view creation and view-field creation are gated independently, so a retry after a partial failure still backfills the missing view fields of an already-committed view.', +}) +export class DemoteAndBackfillApplicationIndexViewCommand extends ProvisionedWorkspaceCommandRunner { + constructor( + protected readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly applicationService: ApplicationService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + // eslint-disable-next-line twenty/prefer-workspace-scoped-repository + @InjectRepository(ViewEntity) + private readonly viewRepository: Repository, + ) { + super(workspaceIteratorService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + const { + flatViewMaps, + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatViewMaps', + 'flatViewFieldMaps', + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const { twentyStandardFlatApplication, workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + const engineOwnedApplicationUniversalIdentifiers = new Set([ + twentyStandardFlatApplication.universalIdentifier, + workspaceCustomFlatApplication.universalIdentifier, + ]); + + const viewIdsToDemote: string[] = []; + + const objectUniversalIdentifiersWithEngineIndexView = new Set(); + + for (const flatView of Object.values(flatViewMaps.byUniversalIdentifier)) { + if ( + !isDefined(flatView) || + flatView.key !== ViewKey.INDEX || + isDefined(flatView.deletedAt) || + engineOwnedApplicationUniversalIdentifiers.has( + flatView.applicationUniversalIdentifier, + ) + ) { + continue; + } + + if (flatView.isSystemSideEffect === true) { + objectUniversalIdentifiersWithEngineIndexView.add( + flatView.objectMetadataUniversalIdentifier, + ); + continue; + } + + viewIdsToDemote.push(flatView.id); + } + + const backfillOperationsByApplication = + this.computeBackfillOperationsByApplication({ + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + engineOwnedApplicationUniversalIdentifiers, + objectUniversalIdentifiersWithEngineIndexView, + }); + + const { backfillViewCount, backfillViewFieldCount } = [ + ...backfillOperationsByApplication.values(), + ].reduce( + (counts, { viewsToCreate, viewFieldsToCreate }) => ({ + backfillViewCount: counts.backfillViewCount + viewsToCreate.length, + backfillViewFieldCount: + counts.backfillViewFieldCount + viewFieldsToCreate.length, + }), + { backfillViewCount: 0, backfillViewFieldCount: 0 }, + ); + + if ( + viewIdsToDemote.length === 0 && + backfillViewCount === 0 && + backfillViewFieldCount === 0 + ) { + this.logger.log( + `No application INDEX view to demote or backfill for workspace ${workspaceId}`, + ); + + return; + } + + this.logger.log( + `${isDryRun ? '[DRY RUN] ' : ''}Demoting ${viewIdsToDemote.length} application INDEX view(s) and backfilling ${backfillViewCount} engine INDEX view(s) with ${backfillViewFieldCount} view field(s) for workspace ${workspaceId}`, + ); + + if (isDryRun) { + return; + } + + if (viewIdsToDemote.length > 0) { + // Single bulk statement, atomic on its own: every row gets the same + // patch. + await this.viewRepository.update( + { id: In(viewIdsToDemote), workspaceId }, + { key: null }, + ); + + await invalidateIndexViewReconcileCache({ + workspaceId, + workspaceMigrationRunnerService: this.workspaceMigrationRunnerService, + }); + } + + await this.runBackfillMigrations({ + workspaceId, + backfillOperationsByApplication, + }); + + this.logger.log( + `Demoted ${viewIdsToDemote.length} application INDEX view(s) and backfilled ${backfillViewCount} engine INDEX view(s) with ${backfillViewFieldCount} view field(s) for workspace ${workspaceId}`, + ); + } + + private computeBackfillOperationsByApplication({ + flatViewFieldMaps, + flatObjectMetadataMaps, + flatFieldMetadataMaps, + engineOwnedApplicationUniversalIdentifiers, + objectUniversalIdentifiersWithEngineIndexView, + }: { + flatViewFieldMaps: AllFlatEntityMaps['flatViewFieldMaps']; + flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; + flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; + engineOwnedApplicationUniversalIdentifiers: Set; + objectUniversalIdentifiersWithEngineIndexView: Set; + }): BackfillOperationsByApplication { + const backfillOperationsByApplication: BackfillOperationsByApplication = + new Map(); + + const getApplicationBucket = (applicationUniversalIdentifier: string) => { + const existingBucket = backfillOperationsByApplication.get( + applicationUniversalIdentifier, + ); + + if (isDefined(existingBucket)) { + return existingBucket; + } + + const newBucket = { viewsToCreate: [], viewFieldsToCreate: [] }; + + backfillOperationsByApplication.set( + applicationUniversalIdentifier, + newBucket, + ); + + return newBucket; + }; + + for (const flatObjectMetadata of Object.values( + flatObjectMetadataMaps.byUniversalIdentifier, + )) { + if ( + !isDefined(flatObjectMetadata) || + engineOwnedApplicationUniversalIdentifiers.has( + flatObjectMetadata.applicationUniversalIdentifier, + ) + ) { + continue; + } + + const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: + flatObjectMetadata.applicationUniversalIdentifier, + objectUniversalIdentifier: flatObjectMetadata.universalIdentifier, + viewKey: ViewKey.INDEX, + }); + + if ( + !objectUniversalIdentifiersWithEngineIndexView.has( + flatObjectMetadata.universalIdentifier, + ) + ) { + // computeFlatIndexViewToCreate derives the same universal identifier. + getApplicationBucket( + flatObjectMetadata.applicationUniversalIdentifier, + ).viewsToCreate.push( + computeFlatIndexViewToCreate({ + objectMetadata: flatObjectMetadata, + applicationUniversalIdentifier: + flatObjectMetadata.applicationUniversalIdentifier, + }), + ); + } + + const { labelIdentifierFieldMetadataUniversalIdentifier } = + flatObjectMetadata; + + const displayableFlatFieldMetadatas = + flatObjectMetadata.fieldUniversalIdentifiers + .map( + (fieldUniversalIdentifier) => + flatFieldMetadataMaps.byUniversalIdentifier[ + fieldUniversalIdentifier + ], + ) + .filter(isDefined) + .filter((flatFieldMetadata) => + isFlatFieldMetadataDisplayableInDefaultView({ + flatFieldMetadata, + labelIdentifierFieldMetadataUniversalIdentifier, + }), + ); + + // The label identifier view field must be strictly lowest and visible. + const orderedDisplayableFlatFieldMetadatas = + orderFlatFieldMetadatasForSystemIndexView({ + labelIdentifierFieldMetadataUniversalIdentifier, + flatFieldMetadatas: displayableFlatFieldMetadatas, + }); + + const createdAt = new Date().toISOString(); + + orderedDisplayableFlatFieldMetadatas.forEach( + (flatFieldMetadata, position) => { + const fieldApplicationUniversalIdentifier = + flatFieldMetadata.applicationUniversalIdentifier; + + const viewFieldUniversalIdentifier = getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: fieldApplicationUniversalIdentifier, + viewUniversalIdentifier: indexViewUniversalIdentifier, + fieldMetadataUniversalIdentifier: + flatFieldMetadata.universalIdentifier, + }); + + // Already backfilled by a previous (partially failed) run. + if ( + isDefined( + flatViewFieldMaps.byUniversalIdentifier[ + viewFieldUniversalIdentifier + ], + ) + ) { + return; + } + + getApplicationBucket( + fieldApplicationUniversalIdentifier, + ).viewFieldsToCreate.push({ + universalIdentifier: viewFieldUniversalIdentifier, + applicationUniversalIdentifier: fieldApplicationUniversalIdentifier, + fieldMetadataUniversalIdentifier: + flatFieldMetadata.universalIdentifier, + viewUniversalIdentifier: indexViewUniversalIdentifier, + viewFieldGroupUniversalIdentifier: null, + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + position, + aggregateOperation: null, + isActive: true, + isSystemSideEffect: true, + universalOverrides: null, + createdAt, + updatedAt: createdAt, + deletedAt: null, + }); + }, + ); + } + + return backfillOperationsByApplication; + } + + private async runBackfillMigrations({ + workspaceId, + backfillOperationsByApplication, + }: { + workspaceId: string; + backfillOperationsByApplication: BackfillOperationsByApplication; + }): Promise { + for (const [ + applicationUniversalIdentifier, + { viewsToCreate }, + ] of backfillOperationsByApplication.entries()) { + if (viewsToCreate.length === 0) { + continue; + } + + await this.runBackfillMigration({ + workspaceId, + applicationUniversalIdentifier, + allFlatEntityOperationByMetadataName: { + view: { + flatEntityToCreate: viewsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + }); + } + + for (const [ + applicationUniversalIdentifier, + { viewFieldsToCreate }, + ] of backfillOperationsByApplication.entries()) { + if (viewFieldsToCreate.length === 0) { + continue; + } + + await this.runBackfillMigration({ + workspaceId, + applicationUniversalIdentifier, + allFlatEntityOperationByMetadataName: { + viewField: { + flatEntityToCreate: viewFieldsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + }); + } + } + + private async runBackfillMigration({ + workspaceId, + applicationUniversalIdentifier, + allFlatEntityOperationByMetadataName, + }: { + workspaceId: string; + applicationUniversalIdentifier: string; + allFlatEntityOperationByMetadataName: Record< + string, + { + flatEntityToCreate: unknown[]; + flatEntityToDelete: never[]; + flatEntityToUpdate: never[]; + } + >; + }): Promise { + const result = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunLegacyWorkspaceMigration( + { + isSystemBuild: true, + workspaceId, + applicationUniversalIdentifier, + allFlatEntityOperationByMetadataName: + allFlatEntityOperationByMetadataName as never, + }, + ); + + if (result.status === 'fail') { + this.logger.error( + `Failed to backfill engine INDEX view(s) for application ${applicationUniversalIdentifier} in workspace ${workspaceId}:\n${JSON.stringify(result, null, 2)}`, + ); + + throw new Error( + `Failed to backfill engine INDEX view(s) for workspace ${workspaceId}`, + ); + } + } +} 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 new file mode 100644 index 0000000000..9a047355e4 --- /dev/null +++ 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 @@ -0,0 +1,490 @@ +import { + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; + +import { type WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { ReconcileIndexViewUniversalIdentifierCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255689000-reconcile-index-view-universal-identifier.command'; +import { type ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { type WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { type WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +const WORKSPACE_ID = '20202020-0000-4000-8000-000000000001'; +const STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000ad'; +const CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000aa'; +const EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000ae'; +const STANDARD_OBJECT_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000bb'; +const CUSTOM_OBJECT_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000bc'; +const FIELD_UNIVERSAL_IDENTIFIER = '20202020-0000-4000-8000-0000000000cc'; + +const DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER = + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: 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, + viewUniversalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + }); + +const STANDARD_OBJECT_METADATA = { + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, +}; + +// A custom object: its INDEX view was historically created with a random v4 +// universal identifier by ObjectMetadataService.createOneObject. +const CUSTOM_OBJECT_METADATA = { + universalIdentifier: CUSTOM_OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, +}; + +type WorkspaceView = { + id: string; + universalIdentifier: string; + key: ViewKey | null; + applicationUniversalIdentifier?: string; + deletedAt?: string | null; + isSystemSideEffect?: boolean; + objectMetadataUniversalIdentifier?: string; + viewFieldUniversalIdentifiers?: string[]; +}; + +type WorkspaceViewField = { + id: string; + universalIdentifier: string; + applicationUniversalIdentifier?: string; + fieldMetadataUniversalIdentifier?: string; + deletedAt?: string | null; + isSystemSideEffect?: boolean; +}; + +const buildFlatView = (view: WorkspaceView) => ({ + applicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + deletedAt: null, + isSystemSideEffect: false, + objectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIER, + viewFieldUniversalIdentifiers: [], + ...view, +}); + +const buildFlatViewField = (viewField: WorkspaceViewField) => ({ + applicationUniversalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + deletedAt: null, + isSystemSideEffect: false, + ...viewField, +}); + +const buildFlatFieldMetadata = ({ + applicationUniversalIdentifier = STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, +}: { + applicationUniversalIdentifier?: string; +} = {}) => ({ + universalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier, +}); + +const buildByUniversalIdentifierMap = < + T extends { universalIdentifier: string }, +>( + flatEntities: T[], +) => ({ + byUniversalIdentifier: Object.fromEntries( + flatEntities.map((flatEntity) => [ + flatEntity.universalIdentifier, + flatEntity, + ]), + ), +}); + +describe('ReconcileIndexViewUniversalIdentifierCommand', () => { + let command: ReconcileIndexViewUniversalIdentifierCommand; + let getOrRecomputeMock: jest.Mock; + let invalidateCacheMock: jest.Mock; + let viewUpdateMock: jest.Mock; + let viewFieldUpdateMock: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + + getOrRecomputeMock = jest.fn(); + invalidateCacheMock = jest.fn().mockResolvedValue(undefined); + viewUpdateMock = jest.fn().mockResolvedValue(undefined); + viewFieldUpdateMock = jest.fn().mockResolvedValue(undefined); + + const entityManagerMock = { + getRepository: (entity: unknown) => { + if (entity === ViewEntity) { + return { update: viewUpdateMock }; + } + if (entity === ViewFieldEntity) { + return { update: viewFieldUpdateMock }; + } + throw new Error('Unexpected repository'); + }, + }; + + const viewRepositoryMock = { + manager: { + transaction: jest.fn( + async (callback: (entityManager: unknown) => Promise) => + callback(entityManagerMock), + ), + }, + }; + + command = new ReconcileIndexViewUniversalIdentifierCommand( + {} as WorkspaceIteratorService, + { + getOrRecompute: getOrRecomputeMock, + } as unknown as WorkspaceCacheService, + { + invalidateCache: invalidateCacheMock, + } as unknown as WorkspaceMigrationRunnerService, + { + findWorkspaceTwentyStandardAndCustomApplicationOrThrow: jest + .fn() + .mockResolvedValue({ + twentyStandardFlatApplication: { + universalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + }, + workspaceCustomFlatApplication: { + universalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + }, + }), + } as unknown as ApplicationService, + viewRepositoryMock as never, + ); + }); + + const mockWorkspaceCache = ({ + views, + viewFields = [], + fieldMetadatas = [buildFlatFieldMetadata()], + }: { + views: ReturnType[]; + viewFields?: ReturnType[]; + fieldMetadatas?: ReturnType[]; + }) => { + getOrRecomputeMock.mockResolvedValue({ + flatViewMaps: buildByUniversalIdentifierMap(views), + flatViewFieldMaps: buildByUniversalIdentifierMap(viewFields), + flatObjectMetadataMaps: buildByUniversalIdentifierMap([ + STANDARD_OBJECT_METADATA, + CUSTOM_OBJECT_METADATA, + ]), + flatFieldMetadataMaps: buildByUniversalIdentifierMap(fieldMetadatas), + }); + }; + + const runOnWorkspace = (dryRun = false) => + command.runOnWorkspace({ + workspaceId: WORKSPACE_ID, + options: { dryRun }, + index: 0, + total: 1, + }); + + it('re-owns an underived standard INDEX view and its view fields', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: 'legacy-view-uid', + key: ViewKey.INDEX, + viewFieldUniversalIdentifiers: ['legacy-view-field-uid'], + }), + ], + viewFields: [ + buildFlatViewField({ + id: 'view-field-id', + universalIdentifier: 'legacy-view-field-uid', + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).toHaveBeenCalledTimes(1); + expect(viewUpdateMock).toHaveBeenCalledWith( + { id: 'view-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ); + expect(viewFieldUpdateMock).toHaveBeenCalledTimes(1); + expect(viewFieldUpdateMock).toHaveBeenCalledWith( + { id: 'view-field-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ); + expect(invalidateCacheMock).toHaveBeenCalledTimes(1); + expect(invalidateCacheMock.mock.calls[0][0].allFlatEntityMapsKeys).toEqual( + expect.arrayContaining([ + 'flatViewMaps', + 'flatViewFieldMaps', + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + 'flatPageLayoutWidgetMaps', + ]), + ); + }); + + it('re-owns a custom object INDEX view onto the workspace-custom derivation', async () => { + const derivedCustomViewUniversalIdentifier = + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: CUSTOM_OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, + }); + + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'custom-view-id', + universalIdentifier: 'legacy-v4-view-uid', + key: ViewKey.INDEX, + applicationUniversalIdentifier: + CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + objectMetadataUniversalIdentifier: CUSTOM_OBJECT_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).toHaveBeenCalledTimes(1); + expect(viewUpdateMock).toHaveBeenCalledWith( + { id: 'custom-view-id', workspaceId: WORKSPACE_ID }, + { universalIdentifier: derivedCustomViewUniversalIdentifier }, + ); + }); + + it('ignores INDEX views owned by other applications', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'external-view-id', + universalIdentifier: 'external-view-uid', + key: ViewKey.INDEX, + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + }); + + it('skips a soft-deleted INDEX view coexisting with an active one on the same object', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'soft-deleted-view-id', + universalIdentifier: 'soft-deleted-view-uid', + key: ViewKey.INDEX, + deletedAt: '2024-01-01T00:00:00.000Z', + }), + buildFlatView({ + id: 'active-view-id', + universalIdentifier: 'active-view-uid', + key: ViewKey.INDEX, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).toHaveBeenCalledTimes(1); + expect(viewUpdateMock).toHaveBeenCalledWith( + { id: 'active-view-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ); + }); + + it('re-owns only the active row of a soft-deleted + re-created view field pair', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: 'legacy-view-uid', + key: ViewKey.INDEX, + viewFieldUniversalIdentifiers: [ + 'soft-deleted-view-field-uid', + 'active-view-field-uid', + ], + }), + ], + viewFields: [ + buildFlatViewField({ + id: 'soft-deleted-view-field-id', + universalIdentifier: 'soft-deleted-view-field-uid', + deletedAt: '2024-01-01T00:00:00.000Z', + }), + buildFlatViewField({ + id: 'active-view-field-id', + universalIdentifier: 'active-view-field-uid', + }), + ], + }); + + await runOnWorkspace(); + + expect(viewFieldUpdateMock).toHaveBeenCalledTimes(1); + expect(viewFieldUpdateMock).toHaveBeenCalledWith( + { id: 'active-view-field-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ); + }); + + it('re-owns an external application view field living on a standard INDEX view', async () => { + // e.g. an app that declared explicit columns on a standard INDEX view: + // those must converge on the derived scheme so manifest deletion inference + // never drops them once the app stops declaring them. + const derivedExternalViewFieldUniversalIdentifier = + getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + }); + + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: 'legacy-view-uid', + key: ViewKey.INDEX, + viewFieldUniversalIdentifiers: ['external-app-view-field-uid'], + }), + ], + viewFields: [ + buildFlatViewField({ + id: 'external-app-view-field-id', + universalIdentifier: 'external-app-view-field-uid', + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + }), + ], + fieldMetadatas: [ + buildFlatFieldMetadata({ + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewFieldUpdateMock).toHaveBeenCalledWith( + { id: 'external-app-view-field-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: derivedExternalViewFieldUniversalIdentifier, + isSystemSideEffect: true, + }, + ); + }); + + it('derives a view field from the displayed field application, not the row attribution', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: 'legacy-view-uid', + key: ViewKey.INDEX, + viewFieldUniversalIdentifiers: ['user-shown-view-field-uid'], + }), + ], + viewFields: [ + buildFlatViewField({ + id: 'user-shown-view-field-id', + universalIdentifier: 'user-shown-view-field-uid', + applicationUniversalIdentifier: + CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewFieldUpdateMock).toHaveBeenCalledWith( + { id: 'user-shown-view-field-id', workspaceId: WORKSPACE_ID }, + { + universalIdentifier: DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ); + }); + + it('does nothing when the INDEX view and its view fields are already derived and system-owned', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: DERIVED_STANDARD_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + viewFieldUniversalIdentifiers: [ + DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + ], + }), + ], + viewFields: [ + buildFlatViewField({ + id: 'view-field-id', + universalIdentifier: DERIVED_STANDARD_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(viewFieldUpdateMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + }); + + it('does not write in dry-run mode', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'view-id', + universalIdentifier: 'legacy-view-uid', + key: ViewKey.INDEX, + }), + ], + }); + + await runOnWorkspace(true); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(viewFieldUpdateMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + }); +}); 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 new file mode 100644 index 0000000000..1ee3df5a0b --- /dev/null +++ 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 @@ -0,0 +1,314 @@ +import { + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; +import { In } from 'typeorm'; + +import { type WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { DemoteAndBackfillApplicationIndexViewCommand } from 'src/database/commands/upgrade-version-command/2-26/2-26-workspace-command-1785255690000-demote-and-backfill-application-index-view.command'; +import { type ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { type WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { type WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +import { type WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +const WORKSPACE_ID = '20202020-0000-4000-8000-000000000001'; +const STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000ad'; +const CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000aa'; +const EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER = + '20202020-0000-4000-8000-0000000000ae'; +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, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, +}); + +// An external application object with one displayable field. +const OBJECT_METADATA = { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + labelIdentifierFieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + fieldUniversalIdentifiers: [FIELD_UNIVERSAL_IDENTIFIER], +}; + +const FIELD_METADATA = { + universalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + name: 'name', + type: FieldMetadataType.TEXT, +}; + +const buildByUniversalIdentifierMap = < + T extends { universalIdentifier: string }, +>( + flatEntities: T[], +) => ({ + byUniversalIdentifier: Object.fromEntries( + flatEntities.map((flatEntity) => [ + flatEntity.universalIdentifier, + flatEntity, + ]), + ), +}); + +type WorkspaceView = { + id: string; + universalIdentifier: string; + key: ViewKey | null; + applicationUniversalIdentifier?: string; + deletedAt?: string | null; + isSystemSideEffect?: boolean; + objectMetadataUniversalIdentifier?: string; +}; + +const buildFlatView = (view: WorkspaceView) => ({ + applicationUniversalIdentifier: EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + deletedAt: null, + isSystemSideEffect: false, + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + ...view, +}); + +describe('DemoteAndBackfillApplicationIndexViewCommand', () => { + let command: DemoteAndBackfillApplicationIndexViewCommand; + let getOrRecomputeMock: jest.Mock; + let invalidateCacheMock: jest.Mock; + let viewUpdateMock: jest.Mock; + let validateBuildAndRunWorkspaceMigrationMock: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + + getOrRecomputeMock = jest.fn(); + invalidateCacheMock = jest.fn().mockResolvedValue(undefined); + viewUpdateMock = jest.fn().mockResolvedValue(undefined); + validateBuildAndRunWorkspaceMigrationMock = jest + .fn() + .mockResolvedValue({ status: 'success' }); + + const viewRepositoryMock = { + update: viewUpdateMock, + }; + + command = new DemoteAndBackfillApplicationIndexViewCommand( + {} as WorkspaceIteratorService, + { + getOrRecompute: getOrRecomputeMock, + } as unknown as WorkspaceCacheService, + { + invalidateCache: invalidateCacheMock, + } as unknown as WorkspaceMigrationRunnerService, + { + findWorkspaceTwentyStandardAndCustomApplicationOrThrow: jest + .fn() + .mockResolvedValue({ + twentyStandardFlatApplication: { + universalIdentifier: STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + }, + workspaceCustomFlatApplication: { + universalIdentifier: CUSTOM_APPLICATION_UNIVERSAL_IDENTIFIER, + }, + }), + } as unknown as ApplicationService, + { + validateBuildAndRunLegacyWorkspaceMigration: + validateBuildAndRunWorkspaceMigrationMock, + } as unknown as WorkspaceMigrationValidateBuildAndRunService, + viewRepositoryMock as never, + ); + }); + + const mockWorkspaceCache = ({ + views = [], + viewFields = [], + }: { + views?: ReturnType[]; + viewFields?: { universalIdentifier: string }[]; + }) => { + getOrRecomputeMock.mockResolvedValue({ + flatViewMaps: buildByUniversalIdentifierMap(views), + flatViewFieldMaps: buildByUniversalIdentifierMap(viewFields), + flatObjectMetadataMaps: buildByUniversalIdentifierMap([OBJECT_METADATA]), + flatFieldMetadataMaps: buildByUniversalIdentifierMap([FIELD_METADATA]), + }); + }; + + const runOnWorkspace = (dryRun = false) => + command.runOnWorkspace({ + workspaceId: WORKSPACE_ID, + options: { dryRun }, + index: 0, + total: 1, + }); + + it('demotes the application INDEX view and backfills the engine-owned one', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'app-view-id', + universalIdentifier: 'app-view-uid', + key: ViewKey.INDEX, + }), + ], + }); + + await runOnWorkspace(); + + // The app view becomes a plain additional view under its manifest + // identifier. + expect(viewUpdateMock).toHaveBeenCalledTimes(1); + expect(viewUpdateMock).toHaveBeenCalledWith( + { id: In(['app-view-id']), workspaceId: WORKSPACE_ID }, + { key: null }, + ); + // Demotions are visible to the backfill pipeline validators. + expect(invalidateCacheMock).toHaveBeenCalledTimes(1); + + // Two-phase: every application's views are committed before any view + // field, so cross-application view fields always find their parent view. + expect(validateBuildAndRunWorkspaceMigrationMock).toHaveBeenCalledTimes(2); + + const [viewPayload, viewFieldPayload] = + validateBuildAndRunWorkspaceMigrationMock.mock.calls.map( + ([payload]) => payload, + ); + + expect(viewPayload.applicationUniversalIdentifier).toBe( + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + ); + expect( + viewPayload.allFlatEntityOperationByMetadataName.view.flatEntityToCreate, + ).toEqual([ + expect.objectContaining({ + universalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + ]); + expect( + viewPayload.allFlatEntityOperationByMetadataName.viewField, + ).toBeUndefined(); + + expect(viewFieldPayload.applicationUniversalIdentifier).toBe( + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + ); + expect( + viewFieldPayload.allFlatEntityOperationByMetadataName.view, + ).toBeUndefined(); + expect( + viewFieldPayload.allFlatEntityOperationByMetadataName.viewField + .flatEntityToCreate, + ).toEqual([ + expect.objectContaining({ + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + }), + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + isVisible: true, + position: 0, + isSystemSideEffect: true, + }), + ]); + }); + + it('backfills an application object that never had an INDEX view', async () => { + mockWorkspaceCache({ views: [] }); + + await runOnWorkspace(); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + expect(validateBuildAndRunWorkspaceMigrationMock).toHaveBeenCalledTimes(2); + }); + + it('is idempotent: a fully backfilled object is neither demoted nor re-backfilled', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'engine-view-id', + universalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + ], + viewFields: [ + { + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + }), + }, + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + }); + + it('backfills the missing view fields of an already-committed engine INDEX view', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'engine-view-id', + universalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + ], + }); + + await runOnWorkspace(); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(validateBuildAndRunWorkspaceMigrationMock).toHaveBeenCalledTimes(1); + + const [payload] = validateBuildAndRunWorkspaceMigrationMock.mock.calls[0]; + + expect(payload.allFlatEntityOperationByMetadataName.view).toBeUndefined(); + expect( + payload.allFlatEntityOperationByMetadataName.viewField.flatEntityToCreate, + ).toEqual([ + expect.objectContaining({ + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: + EXTERNAL_APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: FIELD_UNIVERSAL_IDENTIFIER, + }), + viewUniversalIdentifier: DERIVED_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }), + ]); + }); + + it('does not write in dry-run mode', async () => { + mockWorkspaceCache({ + views: [ + buildFlatView({ + id: 'app-view-id', + universalIdentifier: 'app-view-uid', + key: ViewKey.INDEX, + }), + ], + }); + + await runOnWorkspace(true); + + expect(viewUpdateMock).not.toHaveBeenCalled(); + expect(validateBuildAndRunWorkspaceMigrationMock).not.toHaveBeenCalled(); + expect(invalidateCacheMock).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/utils/invalidate-index-view-reconcile-cache.util.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/utils/invalidate-index-view-reconcile-cache.util.ts new file mode 100644 index 0000000000..d47bb287c2 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-26/utils/invalidate-index-view-reconcile-cache.util.ts @@ -0,0 +1,28 @@ +import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; +import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util'; +import { type WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service'; + +export const invalidateIndexViewReconcileCache = async ({ + workspaceId, + workspaceMigrationRunnerService, +}: { + workspaceId: string; + workspaceMigrationRunnerService: WorkspaceMigrationRunnerService; +}): Promise => { + const reconciledMetadataRelatedNames = [ + 'view', + ...getMetadataRelatedMetadataNames('view'), + 'viewField', + ...getMetadataRelatedMetadataNames('viewField'), + 'pageLayoutWidget', + ] as const; + + await workspaceMigrationRunnerService.invalidateCache({ + allFlatEntityMapsKeys: [ + ...new Set( + reconciledMetadataRelatedNames.map(getMetadataFlatEntityMapsKey), + ), + ], + workspaceId, + }); +}; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts index 460f36b680..5271c3de26 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/workspace-command-provider.module.ts @@ -25,6 +25,7 @@ import { V2_21_UpgradeVersionCommandModule } from 'src/database/commands/upgrade import { V2_22_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-22/2-22-upgrade-version-command.module'; import { V2_23_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-23/2-23-upgrade-version-command.module'; import { V2_25_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-25/2-25-upgrade-version-command.module'; +import { V2_26_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/2-26/2-26-upgrade-version-command.module'; @Module({ imports: [ @@ -53,6 +54,7 @@ import { V2_25_UpgradeVersionCommandModule } from 'src/database/commands/upgrade V2_22_UpgradeVersionCommandModule, V2_23_UpgradeVersionCommandModule, V2_25_UpgradeVersionCommandModule, + V2_26_UpgradeVersionCommandModule, ], }) export class WorkspaceCommandProviderModule {} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts index dfbf1470a9..2503f17201 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts @@ -1,10 +1,10 @@ import { AggregateOperations, ViewCalendarLayout, + ViewKey, ViewOpenRecordIn, ViewType, ViewVisibility, - ViewKey, } from 'twenty-shared/types'; import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util'; @@ -39,7 +39,7 @@ describe('fromViewManifestToUniversalFlatView', () => { expect(result.isCustom).toBe(true); expect(result.visibility).toBe(ViewVisibility.WORKSPACE); expect(result.openRecordIn).toBe(ViewOpenRecordIn.SIDE_PANEL); - expect(result.key).toBe(ViewKey.INDEX); + expect(result.key).toBeNull(); expect(result.createdAt).toBe(now); expect(result.updatedAt).toBe(now); }); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts index 0ee7bad87b..4f118263d4 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts @@ -28,7 +28,7 @@ export const fromViewManifestToUniversalFlatView = ({ isCustom: true, visibility: viewManifest.visibility ?? ViewVisibility.WORKSPACE, openRecordIn: viewManifest.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL, - key: viewManifest.key ?? null, + key: null, kanbanAggregateOperation: viewManifest.kanbanAggregateOperation ?? null, kanbanAggregateOperationFieldMetadataUniversalIdentifier: viewManifest.kanbanAggregateOperationFieldMetadataUniversalIdentifier ?? diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap index e9a89b92c4..03446b1c6d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap @@ -330,7 +330,6 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma }, "view": { "propertiesToCompare": [ - "key", "deletedAt", "createdByUserWorkspaceId", "name", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 89eb1476b1..62d2245a98 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -303,7 +303,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { toStringify: false, universalProperty: undefined, }, - key: { toCompare: true, toStringify: false, universalProperty: undefined }, + key: { toCompare: false, toStringify: false, universalProperty: undefined }, deletedAt: { toCompare: true, toStringify: false, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-side-effect-companion-metadata-names.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-side-effect-companion-metadata-names.constant.ts index f85ab9759b..1915daa510 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-side-effect-companion-metadata-names.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-side-effect-companion-metadata-names.constant.ts @@ -1,8 +1,14 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; export const ALL_METADATA_SIDE_EFFECT_COMPANION_METADATA_NAMES = { - fieldMetadata: ['index', 'searchFieldMetadata'], - objectMetadata: ['fieldMetadata', 'index', 'searchFieldMetadata'], + fieldMetadata: ['index', 'searchFieldMetadata', 'view', 'viewField'], + objectMetadata: [ + 'fieldMetadata', + 'index', + 'searchFieldMetadata', + 'view', + 'viewField', + ], } as const satisfies Partial< Record >; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/__tests__/compute-flat-view-fields-from-fields-widgets.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/__tests__/compute-flat-view-fields-from-fields-widgets.util.spec.ts index ea8e0bb678..5485f5c0c8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/__tests__/compute-flat-view-fields-from-fields-widgets.util.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/__tests__/compute-flat-view-fields-from-fields-widgets.util.spec.ts @@ -790,8 +790,8 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => { }); }); - describe('INDEX view propagation', () => { - it('should add a hidden, flagged view field to the object INDEX view even without a fields widget', () => { + describe('INDEX view non-propagation', () => { + it('should not target the object INDEX view without a fields widget', () => { const result = computeFlatViewFieldsFromFieldsWidgets({ fieldsToCreate: [ { @@ -817,81 +817,10 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => { 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', () => { + it('should still honor a fields widget explicitly targeting the INDEX view', () => { const result = computeFlatViewFieldsFromFieldsWidgets({ fieldsToCreate: [ { @@ -919,6 +848,7 @@ describe('computeFlatViewFieldsFromFieldsWidgets', () => { expect(result).toHaveLength(1); expect(result[0].isVisible).toBe(true); + expect(result[0].viewUniversalIdentifier).toBe('index-view-uid'); }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util.ts index a1a813b9a3..8908241c9a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view-field/utils/compute-flat-view-fields-from-fields-widgets.util.ts @@ -1,4 +1,3 @@ -import { ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; @@ -33,14 +32,13 @@ const isFieldsWidgetConfiguration = ( ); }; +// TODO remove when implementing https://github.com/twentyhq/core-team-issues/issues/2721 const getFieldViewTargets = ({ objectMetadataUniversalIdentifier, flatPageLayoutWidgetMaps, - flatViewMaps, }: { objectMetadataUniversalIdentifier: string; flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps; - flatViewMaps: FlatViewMaps; }): FieldViewTarget[] => { const targets: FieldViewTarget[] = []; const seenViewIds = new Set(); @@ -72,24 +70,6 @@ const getFieldViewTargets = ({ 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; }; @@ -181,7 +161,6 @@ export const computeFlatViewFieldsFromFieldsWidgets = ({ const targets = getFieldViewTargets({ objectMetadataUniversalIdentifier, flatPageLayoutWidgetMaps, - flatViewMaps, }); const fieldsForObject = fieldsToCreate.filter( 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 new file mode 100644 index 0000000000..95cb8b0f18 --- /dev/null +++ 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 @@ -0,0 +1,680 @@ +import { + getFieldUniversalIdentifier, + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; + +import { type AllFlatEntityOperationRecordByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-operation-record-by-metadata-name.type'; +import { FieldIndexViewFieldOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service'; +import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; + +const APPLICATION_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; + +const computeFieldUniversalIdentifier = (name: string) => + getFieldUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + name, + }); + +const NAME_FIELD_UNIVERSAL_IDENTIFIER = computeFieldUniversalIdentifier('name'); +const PRIORITY_FIELD_UNIVERSAL_IDENTIFIER = + computeFieldUniversalIdentifier('priority'); + +const DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER = + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, + }); + +const computeViewFieldUniversalIdentifier = ({ + viewUniversalIdentifier, + fieldMetadataUniversalIdentifier, +}: { + viewUniversalIdentifier: string; + fieldMetadataUniversalIdentifier: string; +}) => + getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier, + fieldMetadataUniversalIdentifier, + }); + +type PendingFieldMetadata = { + universalIdentifier: string; + objectMetadataUniversalIdentifier: string; + applicationUniversalIdentifier: string; + name: string; + type: FieldMetadataType; + isSystemSideEffect: boolean; +}; + +const buildPendingFieldMetadata = ( + name: string, + type: FieldMetadataType = FieldMetadataType.TEXT, + isSystemSideEffect = false, +): PendingFieldMetadata => ({ + universalIdentifier: computeFieldUniversalIdentifier(name), + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + name, + type, + isSystemSideEffect, +}); + +const NAME_FIELD = buildPendingFieldMetadata('name'); +const PRIORITY_FIELD = buildPendingFieldMetadata( + 'priority', + FieldMetadataType.NUMBER, +); + +const OBJECT_METADATA = { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + nameSingular: 'ticket', + labelIdentifierFieldMetadataUniversalIdentifier: + NAME_FIELD_UNIVERSAL_IDENTIFIER, +}; + +type WorkspaceView = { + id: string; + universalIdentifier: string; + key: ViewKey | null; + isActive?: boolean; + deletedAt?: string | null; + isSystemSideEffect?: boolean; + objectMetadataUniversalIdentifier?: string; +}; + +type WorkspaceViewField = { + universalIdentifier: string; + viewId: string; + viewUniversalIdentifier?: string; + fieldMetadataUniversalIdentifier?: string; + position: number; + isActive: boolean; + deletedAt?: string | null; +}; + +const buildArgs = ({ + triggerFieldMetadata, + pendingFieldMetadatas = [], + objectMetadataCreatedInBatch = false, + pendingViews = [], + pendingViewFields = [], + objectMetadataInWorkspace = false, + viewsInWorkspace = [], + viewFieldsInWorkspace = [], +}: { + triggerFieldMetadata: PendingFieldMetadata; + pendingFieldMetadatas?: PendingFieldMetadata[]; + objectMetadataCreatedInBatch?: boolean; + pendingViews?: { universalIdentifier: string; isSystemSideEffect: boolean }[]; + pendingViewFields?: { + universalIdentifier: string; + viewUniversalIdentifier: string; + fieldMetadataUniversalIdentifier: string; + }[]; + objectMetadataInWorkspace?: boolean; + viewsInWorkspace?: WorkspaceView[]; + viewFieldsInWorkspace?: WorkspaceViewField[]; +}): BuildSideEffectsArgs<'fieldMetadata'> => + ({ + flatEntity: triggerFieldMetadata, + allFlatEntityOperationRecordByMetadataName: { + fieldMetadata: { + flatEntityToCreate: Object.fromEntries( + pendingFieldMetadatas.map((pendingFieldMetadata) => [ + pendingFieldMetadata.universalIdentifier, + pendingFieldMetadata, + ]), + ), + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + objectMetadata: { + flatEntityToCreate: objectMetadataCreatedInBatch + ? { [OBJECT_UNIVERSAL_IDENTIFIER]: OBJECT_METADATA } + : {}, + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + view: { + flatEntityToCreate: Object.fromEntries( + pendingViews.map((pendingView) => [ + pendingView.universalIdentifier, + pendingView, + ]), + ), + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + viewField: { + flatEntityToCreate: Object.fromEntries( + pendingViewFields.map((pendingViewField) => [ + pendingViewField.universalIdentifier, + pendingViewField, + ]), + ), + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + } as unknown as AllFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps: { + flatObjectMetadataMaps: { + byUniversalIdentifier: objectMetadataInWorkspace + ? { [OBJECT_UNIVERSAL_IDENTIFIER]: OBJECT_METADATA } + : {}, + }, + flatViewMaps: { + byUniversalIdentifier: Object.fromEntries( + viewsInWorkspace.map((view) => [ + view.universalIdentifier, + { + isActive: true, + deletedAt: null, + isSystemSideEffect: false, + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + ...view, + // Mirrors fromViewEntityToFlatView: the view aggregates the + // view fields pointing at it. + viewFieldUniversalIdentifiers: viewFieldsInWorkspace + .filter((viewField) => viewField.viewId === view.id) + .map((viewField) => viewField.universalIdentifier), + }, + ]), + ), + }, + flatViewFieldMaps: { + byUniversalIdentifier: Object.fromEntries( + viewFieldsInWorkspace.map((viewField) => [ + viewField.universalIdentifier, + viewField, + ]), + ), + }, + }, + context: {}, + }) as unknown as BuildSideEffectsArgs<'fieldMetadata'>; + +// Synced INDEX view: the 2-26 reconcile command re-owns every INDEX view to the +// derived identifier, so the handler resolves it by that identifier alone. +const SYNCED_INDEX_VIEW: WorkspaceView = { + id: 'view-db-id-1', + universalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, +}; + +describe('FieldIndexViewFieldOnCreateSideEffectHandlerService', () => { + const handler = + new (FieldIndexViewFieldOnCreateSideEffectHandlerService as unknown as new () => FieldIndexViewFieldOnCreateSideEffectHandlerService)(); + + describe('object created in the same batch (default view assembly)', () => { + it('should emit a visible view field at position 0 for the label identifier', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD, PRIORITY_FIELD], + objectMetadataCreatedInBatch: true, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].universalIdentifier).toBe( + computeViewFieldUniversalIdentifier({ + viewUniversalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + }), + ); + expect(viewFields[0].viewUniversalIdentifier).toBe( + DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + ); + expect(viewFields[0].position).toBe(0); + expect(viewFields[0].isVisible).toBe(true); + expect(viewFields[0].isSystemSideEffect).toBe(true); + }); + + it('should position a non-label caller field after the label identifier', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [NAME_FIELD, PRIORITY_FIELD], + objectMetadataCreatedInBatch: true, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].position).toBe(1); + }); + + it('should still emit when the pending INDEX view is a system side effect (object handler emission)', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD], + objectMetadataCreatedInBatch: true, + pendingViews: [ + { + universalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + }); + + // The engine owns the INDEX view, so the handler no longer defers to a + // caller-provided one: it emits and lets the engine collision on the view + // surface the conflict. + it('should still emit when the caller provides the pending INDEX view (no override)', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD], + objectMetadataCreatedInBatch: true, + pendingViews: [ + { + universalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + }); + + it('should noop for a non-displayable field (relation) at object creation', () => { + const relationField = buildPendingFieldMetadata( + 'assignee', + FieldMetadataType.RELATION, + ); + + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: relationField, + pendingFieldMetadatas: [NAME_FIELD, relationField], + objectMetadataCreatedInBatch: true, + }), + ); + + expect(result.status).toBe('noop'); + }); + }); + + describe('field created on an existing object (historical createOneField behavior)', () => { + it('should append a hidden view field to the INDEX view resolved by its derived identifier', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + viewFieldsInWorkspace: [ + { + universalIdentifier: 'existing-vf-1', + viewId: SYNCED_INDEX_VIEW.id, + position: 0, + isActive: true, + }, + { + universalIdentifier: 'existing-vf-2', + viewId: SYNCED_INDEX_VIEW.id, + position: 4, + isActive: true, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].universalIdentifier).toBe( + computeViewFieldUniversalIdentifier({ + viewUniversalIdentifier: SYNCED_INDEX_VIEW.universalIdentifier, + fieldMetadataUniversalIdentifier: PRIORITY_FIELD_UNIVERSAL_IDENTIFIER, + }), + ); + expect(viewFields[0].viewUniversalIdentifier).toBe( + SYNCED_INDEX_VIEW.universalIdentifier, + ); + expect(viewFields[0].position).toBe(5); + expect(viewFields[0].isVisible).toBe(false); + expect(viewFields[0].isSystemSideEffect).toBe(true); + }); + + it('should ignore soft-deleted view fields when computing the append position', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + viewFieldsInWorkspace: [ + { + universalIdentifier: 'existing-vf-1', + viewId: SYNCED_INDEX_VIEW.id, + position: 0, + isActive: true, + }, + // A removed column: still in the flat maps but soft-deleted, its + // position must not inflate the append position. + { + universalIdentifier: 'soft-deleted-vf', + viewId: SYNCED_INDEX_VIEW.id, + position: 9, + isActive: true, + deletedAt: '2024-01-01T00:00:00.000Z', + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].position).toBe(1); + }); + + it('should place the label identifier view field strictly below every existing one', () => { + // Relabeling onto a field created in the same sync: existing positions + // start above zero, the label lands one below the lowest. + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + viewFieldsInWorkspace: [ + { + universalIdentifier: 'existing-vf-1', + viewId: SYNCED_INDEX_VIEW.id, + position: 2, + isActive: true, + }, + { + universalIdentifier: 'existing-vf-2', + viewId: SYNCED_INDEX_VIEW.id, + position: 5, + isActive: true, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].isVisible).toBe(true); + expect(viewFields[0].position).toBe(1); + }); + + it('should place the label identifier view field at position 0 on an empty INDEX view', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].isVisible).toBe(true); + expect(viewFields[0].position).toBe(0); + }); + + it('should emit a hidden view field for a relation field', () => { + const relationField = buildPendingFieldMetadata( + 'assignee', + FieldMetadataType.RELATION, + ); + + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: relationField, + pendingFieldMetadatas: [relationField], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields).toHaveLength(1); + expect(viewFields[0].isVisible).toBe(false); + }); + + // The emitted view field is engine-owned whatever the view provenance, so + // manifest sync deletion inference never drops it. + it('should emit an isSystemSideEffect view field on a manifest-owned INDEX view', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [ + { ...SYNCED_INDEX_VIEW, isSystemSideEffect: false }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect(viewFields[0].isSystemSideEffect).toBe(true); + }); + + // View field groups only ever exist on record-page views, never on an INDEX + // view, so the appended view field is always ungrouped. + it('should emit an ungrouped view field', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.values(result.operations.viewField?.flatEntityToCreate ?? {})[0] + .viewFieldGroupUniversalIdentifier, + ).toBeNull(); + }); + + it('should noop when an INDEX view exists but not under its derived identifier (unreconciled workspace)', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [ + { + ...SYNCED_INDEX_VIEW, + universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000042', + }, + ], + }), + ); + + expect(result.status).toBe('noop'); + }); + + it('should noop when the object has no active INDEX view', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [ + { ...SYNCED_INDEX_VIEW, isActive: false }, + { + id: 'other-view-db-id', + universalIdentifier: 'other-view-uid', + key: null, + }, + ], + }), + ); + + expect(result.status).toBe('noop'); + }); + }); + + it('should not count engine-owned fields when positioning caller view fields', () => { + const searchVectorField = buildPendingFieldMetadata( + 'searchVector', + FieldMetadataType.TS_VECTOR, + true, + ); + + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + // searchVector sits in the expanded matrix (emitted by a peer handler) + // but must not shift the caller field positions. + pendingFieldMetadatas: [NAME_FIELD, searchVectorField, PRIORITY_FIELD], + objectMetadataCreatedInBatch: true, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.values(result.operations.viewField?.flatEntityToCreate ?? {})[0] + .position, + ).toBe(1); + }); + + // A second writer claiming the same (view, field) pair is not deduped here: + // it is a genuine conflict left to surface downstream (engine collision, then + // the flat view field validator on the pair). + it('should still emit when a pending view field already covers the same (view, field) pair', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: PRIORITY_FIELD, + pendingFieldMetadatas: [PRIORITY_FIELD], + objectMetadataInWorkspace: true, + viewsInWorkspace: [SYNCED_INDEX_VIEW], + pendingViewFields: [ + { + // e.g. emitted caller-side for a FIELDS widget targeting the view + universalIdentifier: 'random-caller-vf-uid', + viewUniversalIdentifier: SYNCED_INDEX_VIEW.universalIdentifier, + fieldMetadataUniversalIdentifier: + PRIORITY_FIELD_UNIVERSAL_IDENTIFIER, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.values(result.operations.viewField?.flatEntityToCreate ?? {})[0] + .fieldMetadataUniversalIdentifier, + ).toBe(PRIORITY_FIELD_UNIVERSAL_IDENTIFIER); + }); + + it('should fail when the parent object cannot be resolved', () => { + const result = handler.buildSideEffects( + buildArgs({ + triggerFieldMetadata: NAME_FIELD, + pendingFieldMetadatas: [NAME_FIELD], + }), + ); + + expect(result.status).toBe('fail'); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-system-view-fields-on-delete-side-effect-handler.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-system-view-fields-on-delete-side-effect-handler.service.spec.ts new file mode 100644 index 0000000000..91e00364bc --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/__tests__/field-system-view-fields-on-delete-side-effect-handler.service.spec.ts @@ -0,0 +1,102 @@ +import { FieldSystemViewFieldsOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-system-view-fields-on-delete-side-effect-handler.service'; +import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; + +const DELETED_FIELD_UNIVERSAL_IDENTIFIER = + 'd1d2d3d4-d5d6-4000-8000-000000000001'; +const SYSTEM_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000002'; + +const buildArgs = ({ + viewFieldUniversalIdentifiers, + viewFieldsInWorkspace = [], +}: { + viewFieldUniversalIdentifiers: string[]; + viewFieldsInWorkspace?: { + universalIdentifier: string; + isSystemSideEffect: boolean; + }[]; +}): BuildSideEffectsArgs<'fieldMetadata'> => + ({ + flatEntity: { + universalIdentifier: DELETED_FIELD_UNIVERSAL_IDENTIFIER, + viewFieldUniversalIdentifiers, + }, + allFlatEntityOperationRecordByMetadataName: {}, + relatedFlatEntityMaps: { + flatViewFieldMaps: { + byUniversalIdentifier: Object.fromEntries( + viewFieldsInWorkspace.map((viewField) => [ + viewField.universalIdentifier, + { + ...viewField, + fieldMetadataUniversalIdentifier: + DELETED_FIELD_UNIVERSAL_IDENTIFIER, + }, + ]), + ), + }, + }, + context: {}, + }) as unknown as BuildSideEffectsArgs<'fieldMetadata'>; + +describe('FieldSystemViewFieldsOnDeleteSideEffectHandlerService', () => { + const handler = + new (FieldSystemViewFieldsOnDeleteSideEffectHandlerService as unknown as new () => FieldSystemViewFieldsOnDeleteSideEffectHandlerService)(); + + it('should cascade-delete every engine-owned view field displaying the deleted field', () => { + const result = handler.buildSideEffects( + buildArgs({ + viewFieldUniversalIdentifiers: [ + SYSTEM_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + ], + viewFieldsInWorkspace: [ + { + universalIdentifier: SYSTEM_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + { + universalIdentifier: CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.keys(result.operations.viewField?.flatEntityToDelete ?? {}), + ).toEqual([SYSTEM_VIEW_FIELD_UNIVERSAL_IDENTIFIER]); + }); + + it('should noop when the field only has caller-owned view fields', () => { + const result = handler.buildSideEffects( + buildArgs({ + viewFieldUniversalIdentifiers: [CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER], + viewFieldsInWorkspace: [ + { + universalIdentifier: CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + }, + ], + }), + ); + + expect(result.status).toBe('noop'); + }); + + it('should noop when the field has no view field', () => { + const result = handler.buildSideEffects( + buildArgs({ viewFieldUniversalIdentifiers: [] }), + ); + + expect(result.status).toBe('noop'); + }); +}); 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 new file mode 100644 index 0000000000..837cce885f --- /dev/null +++ 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 @@ -0,0 +1,281 @@ +import { Injectable } from '@nestjs/common'; + +import { + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant'; +import { buildFieldSideEffectParentNotFoundFailure } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/utils/build-field-side-effect-parent-not-found-failure.util'; +import { resolveParentFlatObjectMetadataAfterStateForFieldSideEffect } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/utils/resolve-parent-flat-object-metadata-after-state-for-field-side-effect.util'; +import { computeCallerFlatFieldMetadatasForObject } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-caller-flat-field-metadatas-for-object.util'; +import { computeDefaultIndexViewFieldPositionByFieldUniversalIdentifier } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-default-index-view-field-position-by-field-universal-identifier.util'; +import { + type BuildSideEffectsArgs, + MetadataSideEffectHandler, +} from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; +import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; +import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util'; +import { isFlatFieldMetadataDisplayableInDefaultView } from 'src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util'; +import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; +import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type'; + +@Injectable() +export class FieldIndexViewFieldOnCreateSideEffectHandlerService extends MetadataSideEffectHandler( + { + operation: 'create', + metadataName: 'fieldMetadata', + name: 'fieldIndexViewFieldOnCreate', + description: + 'When a field is created, provision its view field on the parent object INDEX view. This handler owns the view fields of every caller-provided field. When the parent object is created in the same batch, the default view assembly applies: only displayable fields (no relations) get a visible view field, positioned before the system-field view fields emitted by objectSystemFieldsAndIndexViewOnCreate; both handlers derive positions from the same caller-input list so no ordering dependency exists. When the parent object pre-exists, every created field (relations included) gets a HIDDEN view field appended to the INDEX view, preserving the historical createOneField behavior previously implemented caller-side. Emitted view fields are always isSystemSideEffect, whatever the provenance of the view they land on: the engine authored them, so manifest sync deletion inference must never drop them. Both branches resolve the INDEX view through its name-free deterministic identifier (object identifier + INDEX key), so the lookup is a single map access and never a scan; workspaces synced before the 2-26 reconcile command hold underived INDEX view identifiers and are simply skipped until it runs. Engine-owned fields (the reserved system fields, the searchVector) never reach this handler: they are emitted as side effects and the engine only triggers handlers on the original input, so their view fields belong to the handler that emits them. Noop when the created field is not displayable in the default view (object created in the same batch) or when the object has no active INDEX view (pre-existing object). The engine owns the INDEX view, so this handler never defers to a caller-provided one: the flat view validator rejects caller-created INDEX views outright, and a second writer claiming the same INDEX view field is a genuine conflict left to surface downstream (engine universal-identifier collision, then the flat view field validator on the (view, field) pair).', + }, +) { + buildSideEffects({ + flatEntity: flatFieldMetadata, + allFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps, + }: BuildSideEffectsArgs<'fieldMetadata'>): MetadataSideEffectResult { + const sourceFlatFieldMetadata = + flatFieldMetadata as UniversalFlatFieldMetadata; + const { objectMetadataUniversalIdentifier } = sourceFlatFieldMetadata; + + const parentFlatObjectMetadata = + resolveParentFlatObjectMetadataAfterStateForFieldSideEffect({ + objectMetadataUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps, + }); + + if (!isDefined(parentFlatObjectMetadata)) { + return buildFieldSideEffectParentNotFoundFailure({ + flatFieldMetadata: sourceFlatFieldMetadata, + operation: 'create', + }); + } + + const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: + parentFlatObjectMetadata.applicationUniversalIdentifier, + objectUniversalIdentifier: objectMetadataUniversalIdentifier, + viewKey: ViewKey.INDEX, + }); + + const parentObjectCreatedInSameBatch = isDefined( + allFlatEntityOperationRecordByMetadataName.objectMetadata + ?.flatEntityToCreate[objectMetadataUniversalIdentifier], + ); + + const flatViewFieldToCreate = parentObjectCreatedInSameBatch + ? this.buildViewFieldForObjectCreatedInSameBatch({ + sourceFlatFieldMetadata, + parentFlatObjectMetadata, + indexViewUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + }) + : this.buildViewFieldForExistingObject({ + sourceFlatFieldMetadata, + parentFlatObjectMetadata, + indexViewUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps, + }); + + if (!isDefined(flatViewFieldToCreate)) { + return { status: 'noop' }; + } + + return { + status: 'success', + operations: { + viewField: { + flatEntityToCreate: { + [flatViewFieldToCreate.universalIdentifier]: flatViewFieldToCreate, + }, + }, + }, + }; + } + + private buildViewFieldForObjectCreatedInSameBatch({ + sourceFlatFieldMetadata, + parentFlatObjectMetadata, + indexViewUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + }: { + sourceFlatFieldMetadata: UniversalFlatFieldMetadata; + parentFlatObjectMetadata: { + applicationUniversalIdentifier: string; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; + }; + indexViewUniversalIdentifier: string; + allFlatEntityOperationRecordByMetadataName: BuildSideEffectsArgs<'fieldMetadata'>['allFlatEntityOperationRecordByMetadataName']; + }): UniversalFlatViewField | undefined { + const { labelIdentifierFieldMetadataUniversalIdentifier } = + parentFlatObjectMetadata; + + if ( + !isFlatFieldMetadataDisplayableInDefaultView({ + flatFieldMetadata: sourceFlatFieldMetadata, + labelIdentifierFieldMetadataUniversalIdentifier, + }) + ) { + return undefined; + } + + const displayableCallerFlatFieldMetadatas = + computeCallerFlatFieldMetadatasForObject({ + objectMetadataUniversalIdentifier: + sourceFlatFieldMetadata.objectMetadataUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + displayableOnly: true, + }); + + const positionByFieldUniversalIdentifier = + computeDefaultIndexViewFieldPositionByFieldUniversalIdentifier({ + applicationUniversalIdentifier: + parentFlatObjectMetadata.applicationUniversalIdentifier, + objectMetadataUniversalIdentifier: + sourceFlatFieldMetadata.objectMetadataUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + displayableCallerFlatFieldMetadatas, + }); + + const position = + positionByFieldUniversalIdentifier.get( + sourceFlatFieldMetadata.universalIdentifier, + ) ?? 0; + + const [flatViewFieldToCreate] = computeFlatViewFieldsToCreate({ + objectFlatFieldMetadatas: [sourceFlatFieldMetadata], + viewUniversalIdentifier: indexViewUniversalIdentifier, + applicationUniversalIdentifier: + sourceFlatFieldMetadata.applicationUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + startPosition: position, + }); + + return flatViewFieldToCreate; + } + + private buildViewFieldForExistingObject({ + sourceFlatFieldMetadata, + parentFlatObjectMetadata, + indexViewUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps, + }: { + sourceFlatFieldMetadata: UniversalFlatFieldMetadata; + parentFlatObjectMetadata: { + applicationUniversalIdentifier: string; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; + }; + indexViewUniversalIdentifier: string; + allFlatEntityOperationRecordByMetadataName: BuildSideEffectsArgs<'fieldMetadata'>['allFlatEntityOperationRecordByMetadataName']; + relatedFlatEntityMaps: BuildSideEffectsArgs<'fieldMetadata'>['relatedFlatEntityMaps']; + }): UniversalFlatViewField | undefined { + const existingIndexFlatView = + relatedFlatEntityMaps.flatViewMaps.byUniversalIdentifier[ + indexViewUniversalIdentifier + ]; + + if ( + !isDefined(existingIndexFlatView) || + !existingIndexFlatView.isActive || + isDefined(existingIndexFlatView.deletedAt) + ) { + return undefined; + } + + const existingActivePositions = + existingIndexFlatView.viewFieldUniversalIdentifiers + .map( + (viewFieldUniversalIdentifier) => + relatedFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier[ + viewFieldUniversalIdentifier + ], + ) + .filter(isDefined) + .filter( + (existingFlatViewField) => + existingFlatViewField.isActive && + !isDefined(existingFlatViewField.deletedAt), + ) + .map((existingFlatViewField) => existingFlatViewField.position); + + const isLabelIdentifierField = + sourceFlatFieldMetadata.universalIdentifier === + parentFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier; + + if (isLabelIdentifierField) { + const lowestExistingPosition = + existingActivePositions.length > 0 + ? Math.min(...existingActivePositions) + : 1; + const [flatLabelViewFieldToCreate] = computeFlatViewFieldsToCreate({ + objectFlatFieldMetadatas: [sourceFlatFieldMetadata], + viewUniversalIdentifier: indexViewUniversalIdentifier, + applicationUniversalIdentifier: + sourceFlatFieldMetadata.applicationUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: + parentFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier, + startPosition: lowestExistingPosition - 1, + }); + + return flatLabelViewFieldToCreate; + } + + const appendBasePosition = + existingActivePositions.reduce( + (maxPosition, position) => Math.max(maxPosition, position), + -1, + ) + 1; + + const callerFlatFieldMetadatas = computeCallerFlatFieldMetadatasForObject({ + objectMetadataUniversalIdentifier: + sourceFlatFieldMetadata.objectMetadataUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: + parentFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + displayableOnly: false, + }); + + const indexAmongCallerFlatFieldMetadatas = Math.max( + callerFlatFieldMetadatas.findIndex( + (callerFlatFieldMetadata) => + callerFlatFieldMetadata.universalIdentifier === + sourceFlatFieldMetadata.universalIdentifier, + ), + 0, + ); + + const createdAt = new Date().toISOString(); + const { applicationUniversalIdentifier } = sourceFlatFieldMetadata; + + return { + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier, + viewUniversalIdentifier: indexViewUniversalIdentifier, + fieldMetadataUniversalIdentifier: + sourceFlatFieldMetadata.universalIdentifier, + }), + applicationUniversalIdentifier, + fieldMetadataUniversalIdentifier: + sourceFlatFieldMetadata.universalIdentifier, + viewUniversalIdentifier: indexViewUniversalIdentifier, + viewFieldGroupUniversalIdentifier: null, + isVisible: false, + size: DEFAULT_VIEW_FIELD_SIZE, + position: appendBasePosition + indexAmongCallerFlatFieldMetadatas, + aggregateOperation: null, + isActive: true, + isSystemSideEffect: true, + universalOverrides: null, + createdAt, + updatedAt: createdAt, + deletedAt: null, + }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-system-view-fields-on-delete-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-system-view-fields-on-delete-side-effect-handler.service.ts new file mode 100644 index 0000000000..fa37330cfe --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-system-view-fields-on-delete-side-effect-handler.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; + +import { + type BuildSideEffectsArgs, + MetadataSideEffectHandler, +} from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; +import { filterSystemSideEffectFlatViewFieldsToDelete } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/filter-system-side-effect-flat-view-fields-to-delete.util'; +import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; + +@Injectable() +export class FieldSystemViewFieldsOnDeleteSideEffectHandlerService extends MetadataSideEffectHandler( + { + operation: 'delete', + metadataName: 'fieldMetadata', + name: 'fieldSystemViewFieldsOnDelete', + description: + 'When a field is deleted, cascade-delete every engine-owned view field displaying it, wherever it lives: the INDEX view fields emitted by fieldIndexViewFieldOnCreate, and the record-page ones. Counterpart of fieldIndexViewFieldOnCreate: the engine authored those view fields, so it owns their deletion. Manifest deletion inference excludes isSystemSideEffect entities, so without this cascade they would only ever disappear through the viewField -> fieldMetadata foreign key, behind the engine back. Caller-authored view fields are NOT touched: they are deleted through normal deletion inference / the field delete transpiler.', + }, +) { + buildSideEffects({ + flatEntity: flatFieldMetadata, + relatedFlatEntityMaps, + }: BuildSideEffectsArgs<'fieldMetadata'>): MetadataSideEffectResult { + const viewFieldToDelete = filterSystemSideEffectFlatViewFieldsToDelete({ + viewFieldUniversalIdentifiers: + flatFieldMetadata.viewFieldUniversalIdentifiers, + flatViewFieldMaps: relatedFlatEntityMaps.flatViewFieldMaps, + }); + + if (Object.keys(viewFieldToDelete).length === 0) { + return { status: 'noop' }; + } + + return { + status: 'success', + operations: { + viewField: { + flatEntityToDelete: viewFieldToDelete, + }, + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/metadata-side-effect-handlers.module.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/metadata-side-effect-handlers.module.ts index 8e8102afd7..40e1eec1fb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/metadata-side-effect-handlers.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/metadata-side-effect-handlers.module.ts @@ -1,12 +1,15 @@ import { Module } from '@nestjs/common'; +import { FieldIndexViewFieldOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-index-view-field-on-create-side-effect-handler.service'; import { FieldSearchFieldMetadataOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-search-field-metadata-on-delete-side-effect-handler.service'; +import { FieldSystemViewFieldsOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-system-view-fields-on-delete-side-effect-handler.service'; import { FieldUniqueBackingIndexOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-unique-backing-index-on-create-side-effect-handler.service'; import { FieldUniqueBackingIndexOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-unique-backing-index-on-delete-side-effect-handler.service'; import { FieldUniqueBackingIndexOnUpdateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/field-metadata/services/field-unique-backing-index-on-update-side-effect-handler.service'; import { ObjectSearchVectorOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-search-vector-on-create-side-effect-handler.service'; +import { ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service'; import { ObjectSearchVectorOnUpdateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-search-vector-on-update-side-effect-handler.service'; -import { ObjectSystemFieldsOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-on-create-side-effect-handler.service'; +import { ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-and-index-view-on-create-side-effect-handler.service'; import { ObjectSystemRelationsOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-relations-on-create-side-effect-handler.service'; import { ObjectSystemRelationsOnUpdateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-relations-on-update-side-effect-handler.service'; import { ObjectSystemSideEffectsOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service'; @@ -17,11 +20,14 @@ import { ObjectSystemSideEffectsOnDeleteSideEffectHandlerService } from 'src/eng FieldUniqueBackingIndexOnUpdateSideEffectHandlerService, FieldUniqueBackingIndexOnDeleteSideEffectHandlerService, FieldSearchFieldMetadataOnDeleteSideEffectHandlerService, - ObjectSystemFieldsOnCreateSideEffectHandlerService, + FieldIndexViewFieldOnCreateSideEffectHandlerService, + FieldSystemViewFieldsOnDeleteSideEffectHandlerService, + ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService, ObjectSystemRelationsOnCreateSideEffectHandlerService, ObjectSystemRelationsOnUpdateSideEffectHandlerService, ObjectSearchVectorOnCreateSideEffectHandlerService, ObjectSearchVectorOnUpdateSideEffectHandlerService, + ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService, ObjectSystemSideEffectsOnDeleteSideEffectHandlerService, ], }) 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 new file mode 100644 index 0000000000..4219940743 --- /dev/null +++ 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 @@ -0,0 +1,160 @@ +import { getSystemViewUniversalIdentifier } from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; + +import { ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-index-view-label-identifier-on-update-side-effect-handler.service'; +import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; + +const APPLICATION_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; +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, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, +}); + +const NAME_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'e1e2e3e4-e5e6-4000-8000-000000000001'; +const CODE_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'e1e2e3e4-e5e6-4000-8000-000000000002'; + +const buildArgs = ({ + fromLabelIdentifier, + toLabelIdentifier, + viewFieldsInWorkspace = [], +}: { + fromLabelIdentifier: string | null; + toLabelIdentifier: string | null; + viewFieldsInWorkspace?: { + universalIdentifier: string; + fieldMetadataUniversalIdentifier: string; + position: number; + isVisible: boolean; + }[]; +}): BuildSideEffectsArgs<'objectMetadata'> => + ({ + flatEntity: { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + labelIdentifierFieldMetadataUniversalIdentifier: toLabelIdentifier, + }, + allFlatEntityOperationRecordByMetadataName: {}, + relatedFlatEntityMaps: { + flatObjectMetadataMaps: { + byUniversalIdentifier: { + [OBJECT_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + labelIdentifierFieldMetadataUniversalIdentifier: + fromLabelIdentifier, + }, + }, + }, + flatViewMaps: { + byUniversalIdentifier: { + [INDEX_VIEW_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: INDEX_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + isActive: true, + deletedAt: null, + viewFieldUniversalIdentifiers: viewFieldsInWorkspace.map( + (viewField) => viewField.universalIdentifier, + ), + }, + }, + }, + flatViewFieldMaps: { + byUniversalIdentifier: Object.fromEntries( + viewFieldsInWorkspace.map((viewField) => [ + viewField.universalIdentifier, + { + isActive: true, + deletedAt: null, + isSystemSideEffect: true, + ...viewField, + }, + ]), + ), + }, + }, + context: {}, + }) as unknown as BuildSideEffectsArgs<'objectMetadata'>; + +const SYNCED_VIEW_FIELDS = [ + { + universalIdentifier: NAME_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + position: 0, + isVisible: true, + }, + { + universalIdentifier: CODE_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: CODE_FIELD_UNIVERSAL_IDENTIFIER, + position: 5, + isVisible: false, + }, +]; + +describe('ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService', () => { + const handler = + new (ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService as unknown as new () => ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService)(); + + it('should move the new label identifier view field below the lowest and make it visible', () => { + const result = handler.buildSideEffects( + buildArgs({ + fromLabelIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + toLabelIdentifier: CODE_FIELD_UNIVERSAL_IDENTIFIER, + viewFieldsInWorkspace: SYNCED_VIEW_FIELDS, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const updatedCodeViewField = (result.operations.viewField + ?.flatEntityToUpdate ?? {})[CODE_VIEW_FIELD_UNIVERSAL_IDENTIFIER]; + + expect(updatedCodeViewField?.position).toBe(-1); + expect(updatedCodeViewField?.isVisible).toBe(true); + }); + + it('should noop when the label identifier is unchanged', () => { + const result = handler.buildSideEffects( + buildArgs({ + fromLabelIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + toLabelIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + viewFieldsInWorkspace: SYNCED_VIEW_FIELDS, + }), + ); + + expect(result.status).toBe('noop'); + }); + + it('should noop when the new label identifier view field is already visible and strictly lowest', () => { + const result = handler.buildSideEffects( + buildArgs({ + fromLabelIdentifier: CODE_FIELD_UNIVERSAL_IDENTIFIER, + toLabelIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + viewFieldsInWorkspace: SYNCED_VIEW_FIELDS, + }), + ); + + expect(result.status).toBe('noop'); + }); + + it('should noop when the new label identifier has no synced INDEX view field (created in the same sync)', () => { + const result = handler.buildSideEffects( + buildArgs({ + fromLabelIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + toLabelIdentifier: CODE_FIELD_UNIVERSAL_IDENTIFIER, + viewFieldsInWorkspace: [SYNCED_VIEW_FIELDS[0]], + }), + ); + + expect(result.status).toBe('noop'); + }); +}); 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 new file mode 100644 index 0000000000..35eece996b --- /dev/null +++ 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 @@ -0,0 +1,338 @@ +import { + getFieldUniversalIdentifier, + getSystemViewUniversalIdentifier, + getViewFieldUniversalIdentifier, +} from 'twenty-shared/application'; +import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; + +import { type AllFlatEntityOperationRecordByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-operation-record-by-metadata-name.type'; +import { ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-and-index-view-on-create-side-effect-handler.service'; +import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; + +const APPLICATION_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; +const OTHER_OBJECT_UNIVERSAL_IDENTIFIER = + 'b1b2b3b4-b5b6-4000-8000-000000000002'; + +const computeFieldUniversalIdentifier = (name: string) => + getFieldUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + name, + }); + +const NAME_FIELD_UNIVERSAL_IDENTIFIER = computeFieldUniversalIdentifier('name'); +const SEARCH_VECTOR_FIELD_UNIVERSAL_IDENTIFIER = + computeFieldUniversalIdentifier('searchVector'); + +const ALL_SYSTEM_FIELD_NAMES = [ + 'id', + 'createdAt', + 'updatedAt', + 'deletedAt', + 'createdBy', + 'updatedBy', + 'position', +] as const; + +// The subset of system fields the INDEX view displays: id/deletedAt/position are +// filtered out by the view-field builder. +const DISPLAYABLE_SYSTEM_FIELD_NAMES = [ + 'createdAt', + 'updatedAt', + 'createdBy', + 'updatedBy', +] as const; + +const DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER = + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + viewKey: ViewKey.INDEX, + }); + +type PendingFieldMetadata = { + universalIdentifier: string; + objectMetadataUniversalIdentifier: string; + name: string; + type: FieldMetadataType; +}; + +const NAME_FIELD: PendingFieldMetadata = { + universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER, + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + name: 'name', + type: FieldMetadataType.TEXT, +}; + +const buildArgs = ({ + labelIdentifierFieldMetadataUniversalIdentifier = NAME_FIELD_UNIVERSAL_IDENTIFIER, + pendingFieldMetadatas = [], + pendingViewUniversalIdentifiers = [], +}: { + labelIdentifierFieldMetadataUniversalIdentifier?: string | null; + pendingFieldMetadatas?: PendingFieldMetadata[]; + pendingViewUniversalIdentifiers?: string[]; +}): BuildSideEffectsArgs<'objectMetadata'> => + ({ + flatEntity: { + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + nameSingular: 'ticket', + labelIdentifierFieldMetadataUniversalIdentifier, + }, + allFlatEntityOperationRecordByMetadataName: { + fieldMetadata: { + flatEntityToCreate: Object.fromEntries( + pendingFieldMetadatas.map((pendingFieldMetadata) => [ + pendingFieldMetadata.universalIdentifier, + pendingFieldMetadata, + ]), + ), + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + ...(pendingViewUniversalIdentifiers.length > 0 && { + view: { + flatEntityToCreate: Object.fromEntries( + pendingViewUniversalIdentifiers.map((universalIdentifier) => [ + universalIdentifier, + { universalIdentifier }, + ]), + ), + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + }), + } as unknown as AllFlatEntityOperationRecordByMetadataName, + relatedFlatEntityMaps: {}, + context: {}, + }) as unknown as BuildSideEffectsArgs<'objectMetadata'>; + +describe('ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService', () => { + const handler = + new (ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService as unknown as new () => ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService)(); + + it('should synthesize exactly the 7 reserved system fields, never the caller name nor searchVector', () => { + const result = handler.buildSideEffects( + buildArgs({ pendingFieldMetadatas: [NAME_FIELD] }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const createdUniversalIdentifiers = Object.keys( + result.operations.fieldMetadata?.flatEntityToCreate ?? {}, + ); + + expect(createdUniversalIdentifiers).toHaveLength(7); + expect(createdUniversalIdentifiers).not.toContain( + NAME_FIELD_UNIVERSAL_IDENTIFIER, + ); + expect(createdUniversalIdentifiers).not.toContain( + SEARCH_VECTOR_FIELD_UNIVERSAL_IDENTIFIER, + ); + + for (const name of ALL_SYSTEM_FIELD_NAMES) { + expect(createdUniversalIdentifiers).toContain( + computeFieldUniversalIdentifier(name), + ); + } + }); + + it('should provision the INDEX view and the system-field view fields only, positioned after the displayable caller fields', () => { + const result = handler.buildSideEffects( + buildArgs({ pendingFieldMetadatas: [NAME_FIELD] }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewUniversalIdentifiers = Object.keys( + result.operations.view?.flatEntityToCreate ?? {}, + ); + + expect(viewUniversalIdentifiers).toEqual([ + DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + ]); + expect( + (result.operations.view?.flatEntityToCreate ?? {})[ + DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER + ]?.isSystemSideEffect, + ).toBe(true); + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + // Only the 4 displayable system fields (id, deletedAt and position are + // filtered out); the caller name view field is owned by the field creation + // side effect handler. + const expectedFieldUniversalIdentifiers = + DISPLAYABLE_SYSTEM_FIELD_NAMES.map(computeFieldUniversalIdentifier); + + expect( + viewFields + .map((viewField) => viewField.fieldMetadataUniversalIdentifier) + .sort(), + ).toEqual([...expectedFieldUniversalIdentifiers].sort()); + + // Positions are offset past the single displayable caller field (name), + // whose view field the field handler emits at position 0. + expect( + viewFields.map((viewField) => viewField.position).sort((a, b) => a - b), + ).toEqual([1, 2, 3, 4]); + + for (const viewField of viewFields) { + expect(viewField.isSystemSideEffect).toBe(true); + expect(viewField.universalIdentifier).toBe( + getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: + viewField.fieldMetadataUniversalIdentifier, + }), + ); + } + }); + + it('should start system view field positions at 0 when there is no displayable caller field', () => { + const result = handler.buildSideEffects( + buildArgs({ + labelIdentifierFieldMetadataUniversalIdentifier: null, + pendingFieldMetadatas: [], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.values(result.operations.viewField?.flatEntityToCreate ?? {}) + .map((viewField) => viewField.position) + .sort((a, b) => a - b), + ).toEqual([0, 1, 2, 3]); + }); + + // The engine is the sole owner of the INDEX view and always emits it; a + // caller providing one with the same identifier is a conflict left to the + // engine collision downstream, not silently deferred here. + it('should still emit its own INDEX view even when the caller provides one with the same identifier', () => { + const result = handler.buildSideEffects( + buildArgs({ + pendingFieldMetadatas: [NAME_FIELD], + pendingViewUniversalIdentifiers: [ + DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.keys(result.operations.fieldMetadata?.flatEntityToCreate ?? {}), + ).toHaveLength(7); + expect( + Object.keys(result.operations.view?.flatEntityToCreate ?? {}), + ).toEqual([DERIVED_INDEX_VIEW_UNIVERSAL_IDENTIFIER]); + expect( + Object.values(result.operations.viewField?.flatEntityToCreate ?? {}), + ).toHaveLength(4); + }); + + it('should ignore caller fields belonging to other objects when computing the position offset', () => { + const result = handler.buildSideEffects( + buildArgs({ + pendingFieldMetadatas: [ + NAME_FIELD, + { + universalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000001', + objectMetadataUniversalIdentifier: + OTHER_OBJECT_UNIVERSAL_IDENTIFIER, + name: 'otherObjectField', + type: FieldMetadataType.TEXT, + }, + ], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + expect( + viewFields.map((viewField) => viewField.fieldMetadataUniversalIdentifier), + ).not.toContain('c1c2c3c4-c5c6-4000-8000-000000000001'); + + // Offset is 1 (only the name field belongs to the created object), not 2. + expect( + viewFields.map((viewField) => viewField.position).sort((a, b) => a - b), + ).toEqual([1, 2, 3, 4]); + }); + + // An object with no name field (e.g. a manifest object) has its label + // identifier backed by the id system field; its view field must still be + // strictly lowest, ahead of the displayable caller fields. + it('should place the id label-identifier system view field at the strictly lowest position', () => { + const idFieldUniversalIdentifier = computeFieldUniversalIdentifier('id'); + const callerField: PendingFieldMetadata = { + universalIdentifier: computeFieldUniversalIdentifier('firstKey'), + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + name: 'firstKey', + type: FieldMetadataType.TEXT, + }; + + const result = handler.buildSideEffects( + buildArgs({ + labelIdentifierFieldMetadataUniversalIdentifier: + idFieldUniversalIdentifier, + pendingFieldMetadatas: [callerField], + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + const viewFields = Object.values( + result.operations.viewField?.flatEntityToCreate ?? {}, + ); + + const idViewField = viewFields.find( + (viewField) => + viewField.fieldMetadataUniversalIdentifier === + idFieldUniversalIdentifier, + ); + + expect(idViewField?.position).toBe(0); + for (const viewField of viewFields) { + if ( + viewField.fieldMetadataUniversalIdentifier !== + idFieldUniversalIdentifier + ) { + expect(viewField.position).toBeGreaterThan(0); + } + } + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-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-on-create-side-effect-handler.service.spec.ts deleted file mode 100644 index 344b85d115..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-fields-on-create-side-effect-handler.service.spec.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { getFieldUniversalIdentifier } from 'twenty-shared/application'; - -import { type AllFlatEntityOperationRecordByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-operation-record-by-metadata-name.type'; -import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; -import { ObjectSystemFieldsOnCreateSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-on-create-side-effect-handler.service'; - -const APPLICATION_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000001'; -const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; - -const SYSTEM_FIELD_NAMES = [ - 'id', - 'createdAt', - 'updatedAt', - 'deletedAt', - 'createdBy', - 'updatedBy', - 'position', -] as const; - -const NAME_FIELD_UNIVERSAL_IDENTIFIER = getFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - name: 'name', -}); - -const SEARCH_VECTOR_FIELD_UNIVERSAL_IDENTIFIER = getFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - name: 'searchVector', -}); - -const buildArgs = ({ - labelIdentifierFieldMetadataUniversalIdentifier, - allFlatEntityOperationRecordByMetadataName = {} as unknown as AllFlatEntityOperationRecordByMetadataName, - relatedFlatEntityMaps = {}, -}: { - labelIdentifierFieldMetadataUniversalIdentifier: string; - allFlatEntityOperationRecordByMetadataName?: AllFlatEntityOperationRecordByMetadataName; - relatedFlatEntityMaps?: object; -}): BuildSideEffectsArgs<'objectMetadata'> => - ({ - flatEntity: { - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - labelIdentifierFieldMetadataUniversalIdentifier, - }, - allFlatEntityOperationRecordByMetadataName, - relatedFlatEntityMaps, - context: {}, - }) as unknown as BuildSideEffectsArgs<'objectMetadata'>; - -describe('ObjectSystemFieldsOnCreateSideEffectHandlerService', () => { - const handler = - new (ObjectSystemFieldsOnCreateSideEffectHandlerService as unknown as new () => ObjectSystemFieldsOnCreateSideEffectHandlerService)(); - - it('should synthesize exactly the 7 reserved system fields and never the caller-provided name field nor the searchVector field', () => { - const result = handler.buildSideEffects( - buildArgs({ - labelIdentifierFieldMetadataUniversalIdentifier: - NAME_FIELD_UNIVERSAL_IDENTIFIER, - }), - ); - - expect(result.status).toBe('success'); - - if (result.status !== 'success') { - throw new Error('expected success'); - } - - const createdUniversalIdentifiers = Object.keys( - result.operations.fieldMetadata?.flatEntityToCreate ?? {}, - ); - - expect(createdUniversalIdentifiers).toHaveLength(7); - expect(createdUniversalIdentifiers).not.toContain( - NAME_FIELD_UNIVERSAL_IDENTIFIER, - ); - expect(createdUniversalIdentifiers).not.toContain( - SEARCH_VECTOR_FIELD_UNIVERSAL_IDENTIFIER, - ); - - for (const name of SYSTEM_FIELD_NAMES) { - expect(createdUniversalIdentifiers).toContain( - getFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - name, - }), - ); - } - }); - - it('should still emit all system fields even when they already exist in the from-state (dedup delegated to the engine merge)', () => { - const alreadyPresentByUniversalIdentifier: Record = {}; - - for (const name of SYSTEM_FIELD_NAMES) { - const universalIdentifier = getFieldUniversalIdentifier({ - applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - name, - }); - - alreadyPresentByUniversalIdentifier[universalIdentifier] = { - universalIdentifier, - }; - } - - const result = handler.buildSideEffects( - buildArgs({ - labelIdentifierFieldMetadataUniversalIdentifier: - NAME_FIELD_UNIVERSAL_IDENTIFIER, - relatedFlatEntityMaps: { - flatFieldMetadataMaps: { - byUniversalIdentifier: alreadyPresentByUniversalIdentifier, - }, - }, - }), - ); - - expect(result.status).toBe('success'); - - if (result.status !== 'success') { - throw new Error('expected success'); - } - - expect( - Object.keys(result.operations.fieldMetadata?.flatEntityToCreate ?? {}), - ).toHaveLength(7); - }); -}); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-side-effects-on-delete-side-effect-handler.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-side-effects-on-delete-side-effect-handler.service.spec.ts index 3bf2113e06..51f94d296d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-side-effects-on-delete-side-effect-handler.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/__tests__/object-system-side-effects-on-delete-side-effect-handler.service.spec.ts @@ -1,5 +1,5 @@ -import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; import { ObjectSystemSideEffectsOnDeleteSideEffectHandlerService } from 'src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service'; +import { type BuildSideEffectsArgs } from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; const OTHER_OBJECT_UNIVERSAL_IDENTIFIER = @@ -9,21 +9,81 @@ const SYSTEM_FIELD_UNIVERSAL_IDENTIFIER = 'd1d2d3d4-d5d6-4000-8000-000000000001'; const AUTHOR_FIELD_UNIVERSAL_IDENTIFIER = 'd1d2d3d4-d5d6-4000-8000-000000000002'; +const FORWARD_FIELD_UNIVERSAL_IDENTIFIER = + 'd1d2d3d4-d5d6-4000-8000-000000000003'; +const REVERSE_FIELD_UNIVERSAL_IDENTIFIER = + 'd1d2d3d4-d5d6-4000-8000-000000000004'; const GIN_INDEX_UNIVERSAL_IDENTIFIER = 'e1e2e3e4-e5e6-4000-8000-000000000001'; +const REVERSE_INDEX_UNIVERSAL_IDENTIFIER = + 'e1e2e3e4-e5e6-4000-8000-000000000002'; const SEARCH_FIELD_METADATA_UNIVERSAL_IDENTIFIER = 'f1f2f3f4-f5f6-4000-8000-000000000001'; +const INDEX_VIEW_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const INDEX_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000002'; +const CALLER_VIEW_UNIVERSAL_IDENTIFIER = 'a1a2a3a4-a5a6-4000-8000-000000000003'; +const CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000004'; + +type FieldFixture = { + universalIdentifier: string; + isSystemSideEffect: boolean; + objectMetadataUniversalIdentifier?: string; + relationTargetFieldMetadataUniversalIdentifier?: string | null; + viewFieldUniversalIdentifiers?: string[]; +}; + +type ViewFixture = { + universalIdentifier: string; + isSystemSideEffect: boolean; + viewFieldUniversalIdentifiers?: string[]; +}; + +const buildFieldMetadataMaps = (fields: FieldFixture[]) => ({ + byUniversalIdentifier: Object.fromEntries( + fields.map((field) => [ + field.universalIdentifier, + { + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: null, + viewFieldUniversalIdentifiers: [], + ...field, + }, + ]), + ), +}); const buildArgs = ({ - relatedFlatEntityMaps, + fieldUniversalIdentifiers = [], + indexMetadataUniversalIdentifiers = [], + searchFieldMetadataUniversalIdentifiers = [], + viewUniversalIdentifiers = [], + relatedFlatEntityMaps = {}, }: { - relatedFlatEntityMaps: object; + fieldUniversalIdentifiers?: string[]; + indexMetadataUniversalIdentifiers?: string[]; + searchFieldMetadataUniversalIdentifiers?: string[]; + viewUniversalIdentifiers?: string[]; + relatedFlatEntityMaps?: object; }): BuildSideEffectsArgs<'objectMetadata'> => ({ flatEntity: { universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + fieldUniversalIdentifiers, + indexMetadataUniversalIdentifiers, + searchFieldMetadataUniversalIdentifiers, + viewUniversalIdentifiers, }, allFlatEntityOperationRecordByMetadataName: {}, - relatedFlatEntityMaps, + relatedFlatEntityMaps: { + flatObjectMetadataMaps: { byUniversalIdentifier: {} }, + flatFieldMetadataMaps: { byUniversalIdentifier: {} }, + flatIndexMaps: { byUniversalIdentifier: {} }, + flatSearchFieldMetadataMaps: { byUniversalIdentifier: {} }, + flatViewMaps: { byUniversalIdentifier: {} }, + flatViewFieldMaps: { byUniversalIdentifier: {} }, + ...relatedFlatEntityMaps, + }, context: {}, }) as unknown as BuildSideEffectsArgs<'objectMetadata'>; @@ -34,23 +94,25 @@ describe('ObjectSystemSideEffectsOnDeleteSideEffectHandlerService', () => { it('should cascade-delete engine-owned fields, indexes and searchFieldMetadata while leaving author fields untouched', () => { const result = handler.buildSideEffects( buildArgs({ + fieldUniversalIdentifiers: [ + SYSTEM_FIELD_UNIVERSAL_IDENTIFIER, + AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, + ], + indexMetadataUniversalIdentifiers: [GIN_INDEX_UNIVERSAL_IDENTIFIER], + searchFieldMetadataUniversalIdentifiers: [ + SEARCH_FIELD_METADATA_UNIVERSAL_IDENTIFIER, + ], relatedFlatEntityMaps: { - flatFieldMetadataMaps: { - byUniversalIdentifier: { - [SYSTEM_FIELD_UNIVERSAL_IDENTIFIER]: { - universalIdentifier: SYSTEM_FIELD_UNIVERSAL_IDENTIFIER, - isSystemSideEffect: true, - objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - relationTargetObjectMetadataUniversalIdentifier: null, - }, - [AUTHOR_FIELD_UNIVERSAL_IDENTIFIER]: { - universalIdentifier: AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, - isSystemSideEffect: false, - objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - relationTargetObjectMetadataUniversalIdentifier: null, - }, + flatFieldMetadataMaps: buildFieldMetadataMaps([ + { + universalIdentifier: SYSTEM_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, }, - }, + { + universalIdentifier: AUTHOR_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + }, + ]), flatIndexMaps: { byUniversalIdentifier: { [GIN_INDEX_UNIVERSAL_IDENTIFIER]: { @@ -65,7 +127,6 @@ describe('ObjectSystemSideEffectsOnDeleteSideEffectHandlerService', () => { byUniversalIdentifier: { [SEARCH_FIELD_METADATA_UNIVERSAL_IDENTIFIER]: { universalIdentifier: SEARCH_FIELD_METADATA_UNIVERSAL_IDENTIFIER, - objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, }, }, }, @@ -100,35 +161,34 @@ describe('ObjectSystemSideEffectsOnDeleteSideEffectHandlerService', () => { }); it('should cascade-delete the default-relation reverse morph field on a standard object and its join-column index', () => { - const FORWARD_FIELD_UNIVERSAL_IDENTIFIER = - 'd1d2d3d4-d5d6-4000-8000-000000000003'; - const REVERSE_FIELD_UNIVERSAL_IDENTIFIER = - 'd1d2d3d4-d5d6-4000-8000-000000000004'; - const REVERSE_INDEX_UNIVERSAL_IDENTIFIER = - 'e1e2e3e4-e5e6-4000-8000-000000000002'; - const result = handler.buildSideEffects( buildArgs({ + fieldUniversalIdentifiers: [FORWARD_FIELD_UNIVERSAL_IDENTIFIER], relatedFlatEntityMaps: { - flatFieldMetadataMaps: { + flatObjectMetadataMaps: { byUniversalIdentifier: { - [FORWARD_FIELD_UNIVERSAL_IDENTIFIER]: { - universalIdentifier: FORWARD_FIELD_UNIVERSAL_IDENTIFIER, - isSystemSideEffect: true, - objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, - relationTargetObjectMetadataUniversalIdentifier: - OTHER_OBJECT_UNIVERSAL_IDENTIFIER, - }, - [REVERSE_FIELD_UNIVERSAL_IDENTIFIER]: { - universalIdentifier: REVERSE_FIELD_UNIVERSAL_IDENTIFIER, - isSystemSideEffect: true, - objectMetadataUniversalIdentifier: - OTHER_OBJECT_UNIVERSAL_IDENTIFIER, - relationTargetObjectMetadataUniversalIdentifier: - OBJECT_UNIVERSAL_IDENTIFIER, + [OTHER_OBJECT_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: OTHER_OBJECT_UNIVERSAL_IDENTIFIER, + indexMetadataUniversalIdentifiers: [ + REVERSE_INDEX_UNIVERSAL_IDENTIFIER, + ], }, }, }, + flatFieldMetadataMaps: buildFieldMetadataMaps([ + { + universalIdentifier: FORWARD_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + relationTargetFieldMetadataUniversalIdentifier: + REVERSE_FIELD_UNIVERSAL_IDENTIFIER, + }, + { + universalIdentifier: REVERSE_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + objectMetadataUniversalIdentifier: + OTHER_OBJECT_UNIVERSAL_IDENTIFIER, + }, + ]), flatIndexMaps: { byUniversalIdentifier: { [REVERSE_INDEX_UNIVERSAL_IDENTIFIER]: { @@ -145,7 +205,6 @@ describe('ObjectSystemSideEffectsOnDeleteSideEffectHandlerService', () => { }, }, }, - flatSearchFieldMetadataMaps: { byUniversalIdentifier: {} }, }, }), ); @@ -167,27 +226,132 @@ describe('ObjectSystemSideEffectsOnDeleteSideEffectHandlerService', () => { ).toEqual([REVERSE_INDEX_UNIVERSAL_IDENTIFIER]); }); - it('should not delete entities belonging to a different object', () => { + it('should not delete entities the object aggregators do not reference', () => { const result = handler.buildSideEffects( buildArgs({ relatedFlatEntityMaps: { - flatFieldMetadataMaps: { - byUniversalIdentifier: { - [SYSTEM_FIELD_UNIVERSAL_IDENTIFIER]: { - universalIdentifier: SYSTEM_FIELD_UNIVERSAL_IDENTIFIER, - isSystemSideEffect: true, - objectMetadataUniversalIdentifier: - OTHER_OBJECT_UNIVERSAL_IDENTIFIER, - relationTargetObjectMetadataUniversalIdentifier: null, - }, + flatFieldMetadataMaps: buildFieldMetadataMaps([ + { + universalIdentifier: SYSTEM_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + objectMetadataUniversalIdentifier: + OTHER_OBJECT_UNIVERSAL_IDENTIFIER, }, - }, - flatIndexMaps: { byUniversalIdentifier: {} }, - flatSearchFieldMetadataMaps: { byUniversalIdentifier: {} }, + ]), }, }), ); expect(result.status).toBe('noop'); }); + + it('should cascade-delete the engine-owned INDEX view and its view fields while leaving a caller-owned view untouched', () => { + const result = handler.buildSideEffects( + buildArgs({ + viewUniversalIdentifiers: [ + INDEX_VIEW_UNIVERSAL_IDENTIFIER, + CALLER_VIEW_UNIVERSAL_IDENTIFIER, + ], + relatedFlatEntityMaps: { + flatViewMaps: { + byUniversalIdentifier: Object.fromEntries( + ( + [ + { + universalIdentifier: INDEX_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + viewFieldUniversalIdentifiers: [ + INDEX_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + ], + }, + { + universalIdentifier: CALLER_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + viewFieldUniversalIdentifiers: [ + CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + ], + }, + ] satisfies ViewFixture[] + ).map((view) => [view.universalIdentifier, view]), + ), + }, + flatViewFieldMaps: { + byUniversalIdentifier: { + [INDEX_VIEW_FIELD_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: INDEX_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + [CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: CALLER_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: false, + }, + }, + }, + }, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect( + Object.keys(result.operations.view?.flatEntityToDelete ?? {}), + ).toEqual([INDEX_VIEW_UNIVERSAL_IDENTIFIER]); + expect( + Object.keys(result.operations.viewField?.flatEntityToDelete ?? {}), + ).toEqual([INDEX_VIEW_FIELD_UNIVERSAL_IDENTIFIER]); + }); + + it('should cascade-delete a system view field of a deleted field even when it lives on another object view', () => { + const OTHER_OBJECT_VIEW_FIELD_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000006'; + + const result = handler.buildSideEffects( + buildArgs({ + fieldUniversalIdentifiers: [FORWARD_FIELD_UNIVERSAL_IDENTIFIER], + relatedFlatEntityMaps: { + flatFieldMetadataMaps: buildFieldMetadataMaps([ + { + universalIdentifier: FORWARD_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + relationTargetFieldMetadataUniversalIdentifier: + REVERSE_FIELD_UNIVERSAL_IDENTIFIER, + }, + { + universalIdentifier: REVERSE_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + objectMetadataUniversalIdentifier: + OTHER_OBJECT_UNIVERSAL_IDENTIFIER, + viewFieldUniversalIdentifiers: [ + OTHER_OBJECT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + ], + }, + ]), + flatViewFieldMaps: { + byUniversalIdentifier: { + [OTHER_OBJECT_VIEW_FIELD_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: + OTHER_OBJECT_VIEW_FIELD_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + }, + }, + }, + }), + ); + + expect(result.status).toBe('success'); + + if (result.status !== 'success') { + throw new Error('expected success'); + } + + expect(result.operations.view).toBeUndefined(); + expect( + Object.keys(result.operations.viewField?.flatEntityToDelete ?? {}), + ).toEqual([OTHER_OBJECT_VIEW_FIELD_UNIVERSAL_IDENTIFIER]); + }); }); 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 new file mode 100644 index 0000000000..47a54708a2 --- /dev/null +++ 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 @@ -0,0 +1,147 @@ +import { msg, t } from '@lingui/core/macro'; +import { Injectable } from '@nestjs/common'; + +import { getSystemViewUniversalIdentifier } from 'twenty-shared/application'; +import { ViewKey } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; +import { MetadataSideEffectExceptionCode } from 'src/engine/metadata-modules/metadata-side-effect/exceptions/metadata-side-effect-exception-code'; +import { + type BuildSideEffectsArgs, + MetadataSideEffectHandler, +} from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; +import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; +import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type'; +import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type'; + +@Injectable() +export class ObjectIndexViewLabelIdentifierOnUpdateSideEffectHandlerService extends MetadataSideEffectHandler( + { + operation: 'update', + metadataName: 'objectMetadata', + name: 'objectIndexViewLabelIdentifierOnUpdate', + description: + 'When an object label identifier changes onto a pre-existing field, keep that field INDEX view field strictly lowest and visible, as the flat view field validator requires. The engine owns the INDEX view, so this is a system side effect on both the API and manifest-sync paths, and only the engine-owned view field is touched. Relabeling onto a field created in the same operation is handled by fieldIndexViewFieldOnCreate instead (its view field does not exist yet to be updated), so this handler considers only synced view fields. Noop when the label identifier is unchanged, when the object has no engine-owned INDEX view, when the new label identifier has no synced INDEX view field, or when that view field is already visible and strictly lowest.', + }, +) { + buildSideEffects({ + flatEntity, + relatedFlatEntityMaps, + }: BuildSideEffectsArgs<'objectMetadata'>): MetadataSideEffectResult { + const updatedFlatObjectMetadata = flatEntity as UniversalFlatObjectMetadata; + + const existingFlatObjectMetadata = + relatedFlatEntityMaps.flatObjectMetadataMaps.byUniversalIdentifier[ + updatedFlatObjectMetadata.universalIdentifier + ]; + + if (!isDefined(existingFlatObjectMetadata)) { + return { + status: 'fail', + type: 'update', + metadataName: 'objectMetadata', + flatEntityMinimalInformation: { + universalIdentifier: updatedFlatObjectMetadata.universalIdentifier, + } as Partial>, + errors: [ + { + code: MetadataSideEffectExceptionCode.SIDE_EFFECT_PARENT_METADATA_NOT_FOUND, + message: t`Could not resolve the existing object to reconcile its INDEX label identifier view field`, + userFriendlyMessage: msg`The object to update could not be found to reconcile its INDEX view`, + }, + ], + }; + } + + const newLabelIdentifierFieldMetadataUniversalIdentifier = + updatedFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier; + + if ( + existingFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier === + newLabelIdentifierFieldMetadataUniversalIdentifier || + !isDefined(newLabelIdentifierFieldMetadataUniversalIdentifier) + ) { + return { status: 'noop' }; + } + + const indexViewUniversalIdentifier = getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: + updatedFlatObjectMetadata.applicationUniversalIdentifier, + objectUniversalIdentifier: updatedFlatObjectMetadata.universalIdentifier, + viewKey: ViewKey.INDEX, + }); + + const indexFlatView = + relatedFlatEntityMaps.flatViewMaps.byUniversalIdentifier[ + indexViewUniversalIdentifier + ]; + + if ( + !isDefined(indexFlatView) || + indexFlatView.isSystemSideEffect !== true || + !indexFlatView.isActive || + isDefined(indexFlatView.deletedAt) + ) { + return { status: 'noop' }; + } + + const indexFlatViewFields = indexFlatView.viewFieldUniversalIdentifiers + .map( + (viewFieldUniversalIdentifier) => + relatedFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier[ + viewFieldUniversalIdentifier + ], + ) + .filter(isDefined) + .filter( + (flatViewField) => + flatViewField.isActive && !isDefined(flatViewField.deletedAt), + ) as UniversalFlatViewField[]; + + const labelIdentifierFlatViewField = indexFlatViewFields.find( + (flatViewField) => + flatViewField.fieldMetadataUniversalIdentifier === + newLabelIdentifierFieldMetadataUniversalIdentifier, + ); + + if (!isDefined(labelIdentifierFlatViewField)) { + return { status: 'noop' }; + } + + const otherPositions = indexFlatViewFields + .filter( + (flatViewField) => + flatViewField.universalIdentifier !== + labelIdentifierFlatViewField.universalIdentifier, + ) + .map((flatViewField) => flatViewField.position); + + const isAlreadyLowestAndVisible = + labelIdentifierFlatViewField.isVisible && + (otherPositions.length === 0 || + labelIdentifierFlatViewField.position < Math.min(...otherPositions)); + + if (isAlreadyLowestAndVisible) { + return { status: 'noop' }; + } + + const targetPosition = + otherPositions.length === 0 ? 0 : Math.min(...otherPositions) - 1; + + return { + status: 'success', + operations: { + viewField: { + flatEntityToUpdate: { + [labelIdentifierFlatViewField.universalIdentifier]: { + ...labelIdentifierFlatViewField, + position: targetPosition, + isVisible: true, + }, + }, + }, + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-and-index-view-on-create-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-and-index-view-on-create-side-effect-handler.service.ts new file mode 100644 index 0000000000..e73be3d0b3 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-and-index-view-on-create-side-effect-handler.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@nestjs/common'; + +import { fromArrayToUniqueKeyRecord } from 'twenty-shared/utils'; + +import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { computeCallerFlatFieldMetadatasForObject } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-caller-flat-field-metadatas-for-object.util'; +import { computeDefaultIndexViewFieldPositionByFieldUniversalIdentifier } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-default-index-view-field-position-by-field-universal-identifier.util'; +import { + type BuildSideEffectsArgs, + MetadataSideEffectHandler, +} from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; +import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; +import { buildReservedSystemFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-reserved-system-flat-field-metadatas-for-custom-object.util'; +import { computeFlatIndexViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util'; +import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util'; +import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type'; + +@Injectable() +export class ObjectSystemFieldsAndIndexViewOnCreateSideEffectHandlerService extends MetadataSideEffectHandler( + { + operation: 'create', + metadataName: 'objectMetadata', + name: 'objectSystemFieldsAndIndexViewOnCreate', + description: + 'When an object is created, provision (1) its 7 reserved system fields (id, createdAt, updatedAt, deletedAt, createdBy, updatedBy, position; searchVector is handled by objectSearchVectorOnCreate, and the name field is caller-provided) and (2) its default INDEX table view ("All {objectLabelPlural}") with one view field per displayable SYSTEM field only, all isSystemSideEffect so the engine owns their lifecycle. View fields for caller-provided fields are owned by fieldIndexViewFieldOnCreate (the field creation side effect), which positions them before the system view fields; both handlers derive positions from the same caller-input list so the layout is contiguous without any ordering dependency. The view identifier is name-free (object identifier + INDEX view key), so an object rename keeps the same view. The engine is the sole owner of the INDEX view and always emits it: the flat view validator rejects caller-created INDEX views (caller inputs are forced isSystemSideEffect: false) and enforces a single non-deleted INDEX view per object. twenty-standard is not concerned: it synchronizes through the from/to migration path, which never runs the side-effect engine, and authors its own curated INDEX view/fields.', + }, +) { + buildSideEffects({ + flatEntity: flatObjectMetadata, + allFlatEntityOperationRecordByMetadataName, + }: BuildSideEffectsArgs<'objectMetadata'>): MetadataSideEffectResult { + const sourceFlatObjectMetadata = + flatObjectMetadata as UniversalFlatObjectMetadata; + const { applicationUniversalIdentifier, universalIdentifier } = + sourceFlatObjectMetadata; + + const systemFlatFieldMetadatas = Object.values( + buildReservedSystemFlatFieldMetadatasForCustomObject({ + flatObjectMetadata: { + applicationUniversalIdentifier, + universalIdentifier, + }, + }), + ); + + const systemFieldMetadataToCreate: Record< + string, + MetadataUniversalFlatEntity<'fieldMetadata'> + > = {}; + + for (const systemFlatFieldMetadata of systemFlatFieldMetadatas) { + systemFieldMetadataToCreate[systemFlatFieldMetadata.universalIdentifier] = + systemFlatFieldMetadata; + } + + const flatIndexViewToCreate = computeFlatIndexViewToCreate({ + objectMetadata: sourceFlatObjectMetadata, + applicationUniversalIdentifier, + }); + + const displayableCallerFlatFieldMetadatas = + computeCallerFlatFieldMetadatasForObject({ + objectMetadataUniversalIdentifier: universalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: + sourceFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + displayableOnly: true, + }); + + const positionByFieldUniversalIdentifier = + computeDefaultIndexViewFieldPositionByFieldUniversalIdentifier({ + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier: universalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: + sourceFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier, + displayableCallerFlatFieldMetadatas, + }); + + const flatViewFieldsToCreate = computeFlatViewFieldsToCreate({ + objectFlatFieldMetadatas: systemFlatFieldMetadatas, + viewUniversalIdentifier: flatIndexViewToCreate.universalIdentifier, + applicationUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: + sourceFlatObjectMetadata.labelIdentifierFieldMetadataUniversalIdentifier, + }).map((flatViewFieldToCreate) => ({ + ...flatViewFieldToCreate, + position: + positionByFieldUniversalIdentifier.get( + flatViewFieldToCreate.fieldMetadataUniversalIdentifier, + ) ?? flatViewFieldToCreate.position, + })); + + return { + status: 'success', + operations: { + fieldMetadata: { flatEntityToCreate: systemFieldMetadataToCreate }, + view: { + flatEntityToCreate: { + [flatIndexViewToCreate.universalIdentifier]: flatIndexViewToCreate, + }, + }, + viewField: { + flatEntityToCreate: fromArrayToUniqueKeyRecord({ + array: flatViewFieldsToCreate, + uniqueKey: 'universalIdentifier', + }), + }, + }, + }; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-on-create-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-on-create-side-effect-handler.service.ts deleted file mode 100644 index 09f16efbe8..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-fields-on-create-side-effect-handler.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; -import { - type BuildSideEffectsArgs, - MetadataSideEffectHandler, -} from 'src/engine/metadata-modules/metadata-side-effect/interfaces/base-metadata-side-effect-handler.service'; -import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; -import { buildReservedSystemFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-reserved-system-flat-field-metadatas-for-custom-object.util'; - -@Injectable() -export class ObjectSystemFieldsOnCreateSideEffectHandlerService extends MetadataSideEffectHandler( - { - operation: 'create', - metadataName: 'objectMetadata', - name: 'objectSystemFieldsOnCreate', - description: - 'When an object is created, generate its 7 reserved system fields (id, createdAt, updatedAt, deletedAt, createdBy, updatedBy, position). The searchVector field is provisioned by the self-contained objectSearchVectorOnCreate handler alongside its GIN index and searchFieldMetadata. The default name field is NOT synthesized here: it is a caller-provided default field (SDK auto-complete on the manifest path, input transpiler on the API path).', - }, -) { - buildSideEffects({ - flatEntity: flatObjectMetadata, - }: BuildSideEffectsArgs<'objectMetadata'>): MetadataSideEffectResult { - const { applicationUniversalIdentifier, universalIdentifier } = - flatObjectMetadata; - - const reservedSystemFlatFieldMetadatas = - buildReservedSystemFlatFieldMetadatasForCustomObject({ - flatObjectMetadata: { - applicationUniversalIdentifier, - universalIdentifier, - }, - }); - - const flatEntityToCreate: Record< - string, - MetadataUniversalFlatEntity<'fieldMetadata'> - > = {}; - - for (const flatFieldMetadata of Object.values( - reservedSystemFlatFieldMetadatas, - )) { - flatEntityToCreate[flatFieldMetadata.universalIdentifier] = - flatFieldMetadata; - } - - return { - status: 'success', - operations: { - fieldMetadata: { - flatEntityToCreate, - }, - }, - }; - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service.ts index d04c27b89a..ad4cb1065d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/object-metadata/services/object-system-side-effects-on-delete-side-effect-handler.service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { filterSystemSideEffectFlatViewFieldsToDelete } from 'src/engine/metadata-modules/metadata-side-effect/handlers/utils/filter-system-side-effect-flat-view-fields-to-delete.util'; import { type BuildSideEffectsArgs, MetadataSideEffectHandler, @@ -10,6 +11,18 @@ import { import { type MetadataSideEffectOperationsByMetadataName } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-operations-by-metadata-name.type'; import { type MetadataSideEffectResult } from 'src/engine/metadata-modules/metadata-side-effect/types/metadata-side-effect-result.type'; +type RelatedFlatEntityMaps = + BuildSideEffectsArgs<'objectMetadata'>['relatedFlatEntityMaps']; + +type FlatEntityToDelete< + TMetadataName extends + | 'fieldMetadata' + | 'index' + | 'searchFieldMetadata' + | 'view' + | 'viewField', +> = Record>; + @Injectable() export class ObjectSystemSideEffectsOnDeleteSideEffectHandlerService extends MetadataSideEffectHandler( { @@ -17,139 +30,261 @@ export class ObjectSystemSideEffectsOnDeleteSideEffectHandlerService extends Met metadataName: 'objectMetadata', name: 'objectSystemSideEffectsOnDelete', description: - 'When an object is deleted, cascade-delete its engine-owned side effects: the reserved system fields, the default relation fields (forward field on the deleted object and reverse morph field on the standard object), every system index (reverse join-column indexes, the GIN searchVector index), and its searchFieldMetadata rows. The engine is the sole authority for isSystemSideEffect entities on delete: the API object delete transpiler cascades only user-authored fields and indexes, and manifest deletion inference excludes these entities entirely. Caller-provided defaults (e.g. the name field) are NOT engine-owned and are deleted through normal deletion inference / the object delete transpiler.', + 'When an object is deleted, cascade-delete its engine-owned side effects: the reserved system fields, the default relation fields (forward field on the deleted object and reverse morph field on the standard object), every system index (reverse join-column indexes, the GIN searchVector index), its searchFieldMetadata rows, and its engine-owned views (the INDEX table view provisioned by objectSystemFieldsAndIndexViewOnCreate) with their view fields. View fields of the deleted system fields are cascaded too even when they live on another object view, which happens for the reverse relation fields. Every lookup walks a foreign key aggregator down from the deleted object (its fields, indexes, searchFieldMetadatas, views, then their view fields) and indexes into the flat entity maps, so the work is proportional to what the object owns and never to the size of the workspace. The engine is the sole authority for isSystemSideEffect entities on delete: the API object delete transpiler cascades only user-authored fields and indexes, and manifest deletion inference excludes these entities entirely. Caller-provided defaults (e.g. the name field) are NOT engine-owned and are deleted through normal deletion inference / the object delete transpiler.', }, ) { buildSideEffects({ flatEntity: flatObjectMetadata, relatedFlatEntityMaps, }: BuildSideEffectsArgs<'objectMetadata'>): MetadataSideEffectResult { - const objectMetadataUniversalIdentifier = - flatObjectMetadata.universalIdentifier; + const fieldMetadataToDelete = this.computeFieldMetadataToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }); + const flatFieldMetadatasToDelete = Object.values(fieldMetadataToDelete); - const fieldMetadataToDelete: Record< - string, - MetadataUniversalFlatEntity<'fieldMetadata'> - > = {}; - const deletedFieldUniversalIdentifiers = new Set(); + const viewToDelete = this.computeViewToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }); - for (const flatFieldMetadata of Object.values( - relatedFlatEntityMaps.flatFieldMetadataMaps.byUniversalIdentifier, - )) { - if (!isDefined(flatFieldMetadata)) { - continue; - } + const flatEntityToDeleteByMetadataName = { + fieldMetadata: fieldMetadataToDelete, + index: this.computeIndexToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + flatFieldMetadatasToDelete, + }), + searchFieldMetadata: this.computeSearchFieldMetadataToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }), + view: viewToDelete, + viewField: this.computeViewFieldToDelete({ + relatedFlatEntityMaps, + flatViewsToDelete: Object.values(viewToDelete), + flatFieldMetadatasToDelete, + }), + }; - if (flatFieldMetadata.isSystemSideEffect !== true) { - continue; - } + const operations = Object.fromEntries( + Object.entries(flatEntityToDeleteByMetadataName) + .filter( + ([, flatEntityToDelete]) => + Object.keys(flatEntityToDelete).length > 0, + ) + .map(([metadataName, flatEntityToDelete]) => [ + metadataName, + { flatEntityToDelete }, + ]), + ) as MetadataSideEffectOperationsByMetadataName; - const belongsToObject = - flatFieldMetadata.objectMetadataUniversalIdentifier === - objectMetadataUniversalIdentifier; - const targetsObject = - flatFieldMetadata.relationTargetObjectMetadataUniversalIdentifier === - objectMetadataUniversalIdentifier; + if (Object.keys(operations).length === 0) { + return { status: 'noop' }; + } - if (!belongsToObject && !targetsObject) { + return { + status: 'success', + operations, + }; + } + + private computeFieldMetadataToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }: { + flatObjectMetadata: MetadataUniversalFlatEntity<'objectMetadata'>; + relatedFlatEntityMaps: RelatedFlatEntityMaps; + }): FlatEntityToDelete<'fieldMetadata'> { + const fieldMetadataToDelete: FlatEntityToDelete<'fieldMetadata'> = {}; + + for (const fieldUniversalIdentifier of flatObjectMetadata.fieldUniversalIdentifiers) { + const flatFieldMetadata = + relatedFlatEntityMaps.flatFieldMetadataMaps.byUniversalIdentifier[ + fieldUniversalIdentifier + ]; + + if ( + !isDefined(flatFieldMetadata) || + flatFieldMetadata.isSystemSideEffect !== true + ) { continue; } fieldMetadataToDelete[flatFieldMetadata.universalIdentifier] = flatFieldMetadata; - deletedFieldUniversalIdentifiers.add( - flatFieldMetadata.universalIdentifier, - ); - } - const indexToDelete: Record< - string, - MetadataUniversalFlatEntity<'index'> - > = {}; + const { relationTargetFieldMetadataUniversalIdentifier } = + flatFieldMetadata; - for (const flatIndexMetadata of Object.values( - relatedFlatEntityMaps.flatIndexMaps.byUniversalIdentifier, - )) { - if (!isDefined(flatIndexMetadata)) { + if (!isDefined(relationTargetFieldMetadataUniversalIdentifier)) { continue; } - if (flatIndexMetadata.isSystemSideEffect !== true) { - continue; - } - - const belongsToObject = - flatIndexMetadata.objectMetadataUniversalIdentifier === - objectMetadataUniversalIdentifier; - const referencesDeletedField = - flatIndexMetadata.universalFlatIndexFieldMetadatas.some( - (universalFlatIndexFieldMetadata) => - deletedFieldUniversalIdentifiers.has( - universalFlatIndexFieldMetadata.fieldMetadataUniversalIdentifier, - ), - ); - - if (!belongsToObject && !referencesDeletedField) { - continue; - } - - indexToDelete[flatIndexMetadata.universalIdentifier] = flatIndexMetadata; - } - - const searchFieldMetadataToDelete: Record< - string, - MetadataUniversalFlatEntity<'searchFieldMetadata'> - > = {}; - - for (const flatSearchFieldMetadata of Object.values( - relatedFlatEntityMaps.flatSearchFieldMetadataMaps.byUniversalIdentifier, - )) { - if (!isDefined(flatSearchFieldMetadata)) { - continue; - } + const reverseFlatFieldMetadata = + relatedFlatEntityMaps.flatFieldMetadataMaps.byUniversalIdentifier[ + relationTargetFieldMetadataUniversalIdentifier + ]; if ( - flatSearchFieldMetadata.objectMetadataUniversalIdentifier !== - objectMetadataUniversalIdentifier + !isDefined(reverseFlatFieldMetadata) || + reverseFlatFieldMetadata.isSystemSideEffect !== true ) { continue; } + fieldMetadataToDelete[reverseFlatFieldMetadata.universalIdentifier] = + reverseFlatFieldMetadata; + } + + return fieldMetadataToDelete; + } + + private computeIndexToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + flatFieldMetadatasToDelete, + }: { + flatObjectMetadata: MetadataUniversalFlatEntity<'objectMetadata'>; + relatedFlatEntityMaps: RelatedFlatEntityMaps; + flatFieldMetadatasToDelete: MetadataUniversalFlatEntity<'fieldMetadata'>[]; + }): FlatEntityToDelete<'index'> { + const deletedFieldUniversalIdentifiers = new Set( + flatFieldMetadatasToDelete.map( + (flatFieldMetadata) => flatFieldMetadata.universalIdentifier, + ), + ); + + const indexOwnerFlatObjectMetadataByUniversalIdentifier = new Map([ + [flatObjectMetadata.universalIdentifier, flatObjectMetadata], + ]); + + for (const flatFieldMetadata of flatFieldMetadatasToDelete) { + const ownerFlatObjectMetadata = + relatedFlatEntityMaps.flatObjectMetadataMaps.byUniversalIdentifier[ + flatFieldMetadata.objectMetadataUniversalIdentifier + ]; + + if (isDefined(ownerFlatObjectMetadata)) { + indexOwnerFlatObjectMetadataByUniversalIdentifier.set( + ownerFlatObjectMetadata.universalIdentifier, + ownerFlatObjectMetadata, + ); + } + } + + const indexToDelete: FlatEntityToDelete<'index'> = {}; + + for (const indexOwnerFlatObjectMetadata of indexOwnerFlatObjectMetadataByUniversalIdentifier.values()) { + for (const indexUniversalIdentifier of indexOwnerFlatObjectMetadata.indexMetadataUniversalIdentifiers) { + const flatIndexMetadata = + relatedFlatEntityMaps.flatIndexMaps.byUniversalIdentifier[ + indexUniversalIdentifier + ]; + + if ( + !isDefined(flatIndexMetadata) || + flatIndexMetadata.isSystemSideEffect !== true + ) { + continue; + } + + const belongsToObject = + flatIndexMetadata.objectMetadataUniversalIdentifier === + flatObjectMetadata.universalIdentifier; + const referencesDeletedField = + flatIndexMetadata.universalFlatIndexFieldMetadatas.some( + (universalFlatIndexFieldMetadata) => + deletedFieldUniversalIdentifiers.has( + universalFlatIndexFieldMetadata.fieldMetadataUniversalIdentifier, + ), + ); + + if (!belongsToObject && !referencesDeletedField) { + continue; + } + + indexToDelete[flatIndexMetadata.universalIdentifier] = + flatIndexMetadata; + } + } + + return indexToDelete; + } + + private computeSearchFieldMetadataToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }: { + flatObjectMetadata: MetadataUniversalFlatEntity<'objectMetadata'>; + relatedFlatEntityMaps: RelatedFlatEntityMaps; + }): FlatEntityToDelete<'searchFieldMetadata'> { + const searchFieldMetadataToDelete: FlatEntityToDelete<'searchFieldMetadata'> = + {}; + + for (const searchFieldMetadataUniversalIdentifier of flatObjectMetadata.searchFieldMetadataUniversalIdentifiers) { + const flatSearchFieldMetadata = + relatedFlatEntityMaps.flatSearchFieldMetadataMaps.byUniversalIdentifier[ + searchFieldMetadataUniversalIdentifier + ]; + + if (!isDefined(flatSearchFieldMetadata)) { + continue; + } + searchFieldMetadataToDelete[flatSearchFieldMetadata.universalIdentifier] = flatSearchFieldMetadata; } - const hasFieldMetadataToDelete = - Object.keys(fieldMetadataToDelete).length > 0; - const hasIndexToDelete = Object.keys(indexToDelete).length > 0; - const hasSearchFieldMetadataToDelete = - Object.keys(searchFieldMetadataToDelete).length > 0; + return searchFieldMetadataToDelete; + } - if ( - !hasFieldMetadataToDelete && - !hasIndexToDelete && - !hasSearchFieldMetadataToDelete - ) { - return { status: 'noop' }; + private computeViewToDelete({ + flatObjectMetadata, + relatedFlatEntityMaps, + }: { + flatObjectMetadata: MetadataUniversalFlatEntity<'objectMetadata'>; + relatedFlatEntityMaps: RelatedFlatEntityMaps; + }): FlatEntityToDelete<'view'> { + const viewToDelete: FlatEntityToDelete<'view'> = {}; + + for (const viewUniversalIdentifier of flatObjectMetadata.viewUniversalIdentifiers) { + const flatView = + relatedFlatEntityMaps.flatViewMaps.byUniversalIdentifier[ + viewUniversalIdentifier + ]; + + if (!isDefined(flatView) || flatView.isSystemSideEffect !== true) { + continue; + } + + viewToDelete[flatView.universalIdentifier] = flatView; } - const operations: MetadataSideEffectOperationsByMetadataName = { - ...(hasFieldMetadataToDelete && { - fieldMetadata: { flatEntityToDelete: fieldMetadataToDelete }, - }), - ...(hasIndexToDelete && { - index: { flatEntityToDelete: indexToDelete }, - }), - ...(hasSearchFieldMetadataToDelete && { - searchFieldMetadata: { - flatEntityToDelete: searchFieldMetadataToDelete, - }, - }), - }; + return viewToDelete; + } - return { - status: 'success', - operations, - }; + private computeViewFieldToDelete({ + relatedFlatEntityMaps, + flatViewsToDelete, + flatFieldMetadatasToDelete, + }: { + relatedFlatEntityMaps: RelatedFlatEntityMaps; + flatViewsToDelete: MetadataUniversalFlatEntity<'view'>[]; + flatFieldMetadatasToDelete: MetadataUniversalFlatEntity<'fieldMetadata'>[]; + }): FlatEntityToDelete<'viewField'> { + const viewFieldUniversalIdentifiers = [ + ...flatViewsToDelete.flatMap( + (flatView) => flatView.viewFieldUniversalIdentifiers, + ), + ...flatFieldMetadatasToDelete.flatMap( + (flatFieldMetadata) => flatFieldMetadata.viewFieldUniversalIdentifiers, + ), + ]; + + return filterSystemSideEffectFlatViewFieldsToDelete({ + viewFieldUniversalIdentifiers, + flatViewFieldMaps: relatedFlatEntityMaps.flatViewFieldMaps, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-caller-flat-field-metadatas-for-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-caller-flat-field-metadatas-for-object.util.ts new file mode 100644 index 0000000000..69f5e1a856 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-caller-flat-field-metadatas-for-object.util.ts @@ -0,0 +1,35 @@ +import { type AllFlatEntityOperationRecordByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-operation-record-by-metadata-name.type'; +import { isFlatFieldMetadataDisplayableInDefaultView } from 'src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util'; +import { orderFlatFieldMetadatasForSystemIndexView } from 'src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util'; +import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; + +export const computeCallerFlatFieldMetadatasForObject = ({ + objectMetadataUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + allFlatEntityOperationRecordByMetadataName, + displayableOnly, +}: { + objectMetadataUniversalIdentifier: string; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; + allFlatEntityOperationRecordByMetadataName: AllFlatEntityOperationRecordByMetadataName; + displayableOnly: boolean; +}): UniversalFlatFieldMetadata[] => + orderFlatFieldMetadatasForSystemIndexView({ + labelIdentifierFieldMetadataUniversalIdentifier, + flatFieldMetadatas: ( + Object.values( + allFlatEntityOperationRecordByMetadataName.fieldMetadata + ?.flatEntityToCreate ?? {}, + ) as UniversalFlatFieldMetadata[] + ).filter( + (flatFieldMetadata) => + flatFieldMetadata.objectMetadataUniversalIdentifier === + objectMetadataUniversalIdentifier && + !flatFieldMetadata.isSystemSideEffect && + (!displayableOnly || + isFlatFieldMetadataDisplayableInDefaultView({ + flatFieldMetadata, + labelIdentifierFieldMetadataUniversalIdentifier, + })), + ), + }); diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-default-index-view-field-position-by-field-universal-identifier.util.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-default-index-view-field-position-by-field-universal-identifier.util.ts new file mode 100644 index 0000000000..f0fa9ccfbd --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/compute-default-index-view-field-position-by-field-universal-identifier.util.ts @@ -0,0 +1,46 @@ +import { buildReservedSystemFlatFieldMetadatasForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-reserved-system-flat-field-metadatas-for-custom-object.util'; +import { isFlatFieldMetadataDisplayableInDefaultView } from 'src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util'; +import { orderFlatFieldMetadatasForSystemIndexView } from 'src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util'; +import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; + +export const computeDefaultIndexViewFieldPositionByFieldUniversalIdentifier = ({ + applicationUniversalIdentifier, + objectMetadataUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier, + displayableCallerFlatFieldMetadatas, +}: { + applicationUniversalIdentifier: string; + objectMetadataUniversalIdentifier: string; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; + displayableCallerFlatFieldMetadatas: UniversalFlatFieldMetadata[]; +}): Map => { + const displayableSystemFlatFieldMetadatas = Object.values( + buildReservedSystemFlatFieldMetadatasForCustomObject({ + flatObjectMetadata: { + applicationUniversalIdentifier, + universalIdentifier: objectMetadataUniversalIdentifier, + }, + }), + ).filter((flatFieldMetadata) => + isFlatFieldMetadataDisplayableInDefaultView({ + flatFieldMetadata, + labelIdentifierFieldMetadataUniversalIdentifier, + }), + ); + + const orderedDisplayableFlatFieldMetadatas = + orderFlatFieldMetadatasForSystemIndexView({ + labelIdentifierFieldMetadataUniversalIdentifier, + flatFieldMetadatas: [ + ...displayableCallerFlatFieldMetadatas, + ...displayableSystemFlatFieldMetadatas, + ], + }); + + return new Map( + orderedDisplayableFlatFieldMetadatas.map((flatFieldMetadata, position) => [ + flatFieldMetadata.universalIdentifier, + position, + ]), + ); +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/filter-system-side-effect-flat-view-fields-to-delete.util.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/filter-system-side-effect-flat-view-fields-to-delete.util.ts new file mode 100644 index 0000000000..f14934855c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/handlers/utils/filter-system-side-effect-flat-view-fields-to-delete.util.ts @@ -0,0 +1,35 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type MetadataUniversalFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-universal-flat-entity.type'; +import { type UniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-maps.type'; + +export const filterSystemSideEffectFlatViewFieldsToDelete = ({ + viewFieldUniversalIdentifiers, + flatViewFieldMaps, +}: { + viewFieldUniversalIdentifiers: string[]; + flatViewFieldMaps: UniversalFlatEntityMaps< + MetadataUniversalFlatEntity<'viewField'> + >; +}): Record> => { + const viewFieldToDelete: Record< + string, + MetadataUniversalFlatEntity<'viewField'> + > = {}; + + for (const viewFieldUniversalIdentifier of viewFieldUniversalIdentifiers) { + const flatViewField = + flatViewFieldMaps.byUniversalIdentifier[viewFieldUniversalIdentifier]; + + if ( + !isDefined(flatViewField) || + flatViewField.isSystemSideEffect !== true + ) { + continue; + } + + viewFieldToDelete[flatViewField.universalIdentifier] = flatViewField; + } + + return viewFieldToDelete; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/services/__tests__/metadata-side-effect-engine.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/services/__tests__/metadata-side-effect-engine.service.spec.ts new file mode 100644 index 0000000000..7c9b3cf7ea --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/metadata-side-effect/services/__tests__/metadata-side-effect-engine.service.spec.ts @@ -0,0 +1,110 @@ +import { MetadataSideEffectExceptionCode } from 'src/engine/metadata-modules/metadata-side-effect/exceptions/metadata-side-effect-exception-code'; +import { MetadataSideEffectEngineService } from 'src/engine/metadata-modules/metadata-side-effect/services/metadata-side-effect-engine.service'; + +const RESERVED_VIEW_UNIVERSAL_IDENTIFIER = + 'a1a2a3a4-a5a6-4000-8000-000000000001'; +const OBJECT_UNIVERSAL_IDENTIFIER = 'b1b2b3b4-b5b6-4000-8000-000000000001'; + +const systemViewEmittingHandler = { + operation: 'create', + metadataName: 'objectMetadata', + name: 'fakeSystemViewEmitter', + buildSideEffects: () => ({ + status: 'success', + operations: { + view: { + flatEntityToCreate: { + [RESERVED_VIEW_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: RESERVED_VIEW_UNIVERSAL_IDENTIFIER, + isSystemSideEffect: true, + }, + }, + }, + }, + }), +}; + +const buildEngine = () => { + const registry = { + getRegisteredHandlerKeys: () => [ + { operation: 'create', metadataName: 'objectMetadata' }, + ], + getHandlers: (operation: string, metadataName: string) => + operation === 'create' && metadataName === 'objectMetadata' + ? [systemViewEmittingHandler] + : [], + }; + + return new MetadataSideEffectEngineService(registry as never); +}; + +const buildInput = ({ + callerViewUniversalIdentifier, +}: { + callerViewUniversalIdentifier?: string; +}) => + ({ + objectMetadata: { + flatEntityToCreate: { + [OBJECT_UNIVERSAL_IDENTIFIER]: { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + }, + }, + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + view: { + flatEntityToCreate: + callerViewUniversalIdentifier === undefined + ? {} + : { + [callerViewUniversalIdentifier]: { + universalIdentifier: callerViewUniversalIdentifier, + isSystemSideEffect: false, + }, + }, + flatEntityToUpdate: {}, + flatEntityToDelete: {}, + }, + }) as never; + +describe('MetadataSideEffectEngineService reserved identifier collision', () => { + it('should reject a caller-defined entity whose universal identifier collides with a system side effect one', () => { + const engine = buildEngine(); + + const result = engine.expandWithSideEffects({ + allFlatEntityOperationRecordByMetadataName: buildInput({ + callerViewUniversalIdentifier: RESERVED_VIEW_UNIVERSAL_IDENTIFIER, + }), + sideEffectRelatedFlatEntityMaps: {}, + context: {} as never, + }); + + expect(result.status).toBe('fail'); + + if (result.status !== 'fail') { + throw new Error('expected fail'); + } + + const viewFailures = result.report.view; + + expect(viewFailures).toHaveLength(1); + expect(viewFailures[0].errors[0].code).toBe( + MetadataSideEffectExceptionCode.RESERVED_SYSTEM_UNIVERSAL_IDENTIFIER, + ); + }); + + it('should let a caller-defined entity through when its universal identifier does not collide', () => { + const engine = buildEngine(); + + const result = engine.expandWithSideEffects({ + allFlatEntityOperationRecordByMetadataName: buildInput({ + callerViewUniversalIdentifier: 'c1c2c3c4-c5c6-4000-8000-000000000001', + }), + sideEffectRelatedFlatEntityMaps: {}, + context: {} as never, + }); + + expect(result.status).toBe('success'); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index ada18e1047..8113772120 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -2,12 +2,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; -import { - ViewKey, - ViewOpenRecordIn, - ViewType, - ViewVisibility, -} from 'twenty-shared/types'; import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils'; import { FindManyOptions, FindOneOptions, Repository } from 'typeorm'; import { v4, v5 } from 'uuid'; @@ -489,10 +483,12 @@ export class ObjectMetadataService extends TypeOrmQueryService { + it('should derive the INDEX view universal identifier from the object', () => { + const result = computeFlatIndexViewToCreate({ + applicationUniversalIdentifier, + objectMetadata: { universalIdentifier: objectUniversalIdentifier }, + }); + + expect(result.universalIdentifier).toBe( + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier, + objectUniversalIdentifier, + viewKey: ViewKey.INDEX, + }), + ); + }); + + it('should build a system-owned INDEX table view keyed on ViewKey.INDEX', () => { + const result = computeFlatIndexViewToCreate({ + applicationUniversalIdentifier, + objectMetadata: { universalIdentifier: objectUniversalIdentifier }, + }); + + expect(result.key).toBe(ViewKey.INDEX); + expect(result.type).toBe(ViewType.TABLE); + expect(result.name).toBe('All {objectLabelPlural}'); + expect(result.isSystemSideEffect).toBe(true); + expect(result.objectMetadataUniversalIdentifier).toBe( + objectUniversalIdentifier, + ); + expect(result.applicationUniversalIdentifier).toBe( + applicationUniversalIdentifier, + ); + }); + + it('should be deterministic and independent from the view primary key', () => { + const first = computeFlatIndexViewToCreate({ + applicationUniversalIdentifier, + objectMetadata: { universalIdentifier: objectUniversalIdentifier }, + }); + const second = computeFlatIndexViewToCreate({ + applicationUniversalIdentifier, + objectMetadata: { universalIdentifier: objectUniversalIdentifier }, + }); + + expect(first.universalIdentifier).toBe(second.universalIdentifier); + expect(first.id).not.toBe(second.id); + }); +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-view-fields-to-create.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-view-fields-to-create.util.spec.ts index fec9f78bbe..deb108caea 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-view-fields-to-create.util.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/compute-flat-view-fields-to-create.util.spec.ts @@ -1,3 +1,4 @@ +import { getViewFieldUniversalIdentifier } from 'twenty-shared/application'; import { FieldMetadataType } from 'twenty-shared/types'; import { v4 } from 'uuid'; @@ -39,10 +40,7 @@ const makeFieldMetadata = ( } as unknown as UniversalFlatFieldMetadata; }; -const flatApplication = { - universalIdentifier: 'app-uid', - id: 'app-id', -} as never; +const applicationUniversalIdentifier = 'a1a2a3a4-a5a6-4000-8000-000000000001'; const viewUniversalIdentifier = 'view-uid'; @@ -62,7 +60,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: fields, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: null, }); @@ -87,7 +85,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: fields, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: null, }); @@ -116,7 +114,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: fields, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: null, }); @@ -141,7 +139,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: fields, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: null, }); @@ -165,7 +163,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: fields, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: labelField.universalIdentifier, }); @@ -194,7 +192,7 @@ describe('computeFlatViewFieldsToCreate', () => { const result = computeFlatViewFieldsToCreate({ objectFlatFieldMetadatas: [labelField, otherField], viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier: labelField.universalIdentifier, excludeLabelIdentifier: true, @@ -205,4 +203,27 @@ describe('computeFlatViewFieldsToCreate', () => { otherField.universalIdentifier, ); }); + + it('should derive the view field universal identifier deterministically', () => { + const field = makeFieldMetadata({ + name: 'name', + type: FieldMetadataType.TEXT, + }); + + const result = computeFlatViewFieldsToCreate({ + objectFlatFieldMetadatas: [field], + viewUniversalIdentifier, + applicationUniversalIdentifier, + labelIdentifierFieldMetadataUniversalIdentifier: null, + }); + + expect(result[0].universalIdentifier).toBe( + getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier, + viewUniversalIdentifier, + fieldMetadataUniversalIdentifier: field.universalIdentifier, + }), + ); + expect(result[0].isSystemSideEffect).toBe(true); + }); }); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/order-flat-field-metadatas-for-system-index-view.util.spec.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/order-flat-field-metadatas-for-system-index-view.util.spec.ts new file mode 100644 index 0000000000..9c488edcf9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/__tests__/order-flat-field-metadatas-for-system-index-view.util.spec.ts @@ -0,0 +1,54 @@ +import { orderFlatFieldMetadatasForSystemIndexView } from 'src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util'; + +const NAME = { universalIdentifier: 'name-uid' }; +const CREATED_AT = { universalIdentifier: 'created-at-uid' }; +const CREATED_BY = { universalIdentifier: 'created-by-uid' }; + +describe('orderFlatFieldMetadatasForSystemIndexView', () => { + it('should move the label identifier first when it is not already', () => { + expect( + orderFlatFieldMetadatasForSystemIndexView({ + flatFieldMetadatas: [CREATED_AT, NAME, CREATED_BY], + labelIdentifierFieldMetadataUniversalIdentifier: 'name-uid', + }), + ).toEqual([NAME, CREATED_AT, CREATED_BY]); + }); + + it('should preserve the input order of every other field', () => { + expect( + orderFlatFieldMetadatasForSystemIndexView({ + flatFieldMetadatas: [NAME, CREATED_BY, CREATED_AT], + labelIdentifierFieldMetadataUniversalIdentifier: 'name-uid', + }), + ).toEqual([NAME, CREATED_BY, CREATED_AT]); + }); + + it('should keep the input order when the object has no label identifier', () => { + expect( + orderFlatFieldMetadatasForSystemIndexView({ + flatFieldMetadatas: [CREATED_AT, NAME], + labelIdentifierFieldMetadataUniversalIdentifier: null, + }), + ).toEqual([CREATED_AT, NAME]); + }); + + it('should keep the input order when the label identifier is not in the list', () => { + expect( + orderFlatFieldMetadatasForSystemIndexView({ + flatFieldMetadatas: [CREATED_AT, CREATED_BY], + labelIdentifierFieldMetadataUniversalIdentifier: 'name-uid', + }), + ).toEqual([CREATED_AT, CREATED_BY]); + }); + + it('should not mutate the input array', () => { + const flatFieldMetadatas = [CREATED_AT, NAME]; + + orderFlatFieldMetadatasForSystemIndexView({ + flatFieldMetadatas, + labelIdentifierFieldMetadataUniversalIdentifier: 'name-uid', + }); + + expect(flatFieldMetadatas).toEqual([CREATED_AT, NAME]); + }); +}); 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 new file mode 100644 index 0000000000..eb234b914b --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-index-view-to-create.util.ts @@ -0,0 +1,63 @@ +import { getSystemViewUniversalIdentifier } from 'twenty-shared/application'; +import { + ViewKey, + ViewOpenRecordIn, + ViewType, + ViewVisibility, +} from 'twenty-shared/types'; +import { v4 } from 'uuid'; + +import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type'; +import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type'; + +export const computeFlatIndexViewToCreate = ({ + objectMetadata, + applicationUniversalIdentifier, +}: { + applicationUniversalIdentifier: string; + objectMetadata: Pick; +}): UniversalFlatView & { id: string } => { + const createdAt = new Date().toISOString(); + + return { + id: v4(), + objectMetadataUniversalIdentifier: objectMetadata.universalIdentifier, + name: `All {objectLabelPlural}`, + key: ViewKey.INDEX, + icon: 'IconList', + type: ViewType.TABLE, + createdAt, + updatedAt: createdAt, + deletedAt: null, + isCustom: true, + anyFieldFilterValue: null, + calendarFieldMetadataUniversalIdentifier: null, + calendarEndFieldMetadataUniversalIdentifier: null, + calendarLayout: null, + isCompact: false, + shouldHideEmptyGroups: false, + kanbanColumnWidth: null, + kanbanAggregateOperation: null, + kanbanAggregateOperationFieldMetadataUniversalIdentifier: null, + mainGroupByFieldMetadataUniversalIdentifier: null, + openRecordIn: ViewOpenRecordIn.SIDE_PANEL, + position: 0, + universalIdentifier: getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier, + objectUniversalIdentifier: objectMetadata.universalIdentifier, + viewKey: ViewKey.INDEX, + }), + visibility: ViewVisibility.WORKSPACE, + createdByUserWorkspaceId: null, + isActive: true, + isSystemSideEffect: true, + universalOverrides: null, + viewFieldUniversalIdentifiers: [], + viewFieldGroupUniversalIdentifiers: [], + viewFilterUniversalIdentifiers: [], + viewGroupUniversalIdentifiers: [], + viewFilterGroupUniversalIdentifiers: [], + viewSortUniversalIdentifiers: [], + applicationUniversalIdentifier, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util.ts index c30049ace1..2089638945 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util.ts @@ -1,74 +1,61 @@ -import { v4 } from 'uuid'; +import { getViewFieldUniversalIdentifier } from 'twenty-shared/application'; -import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/metadata-modules/flat-view-field/constants/default-view-field-size.constant'; +import { isFlatFieldMetadataDisplayableInDefaultView } from 'src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util'; +import { orderFlatFieldMetadatasForSystemIndexView } from 'src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util'; import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type'; -import { FieldMetadataType } from 'twenty-shared/types'; export const computeFlatViewFieldsToCreate = ({ objectFlatFieldMetadatas, viewUniversalIdentifier, - flatApplication, + applicationUniversalIdentifier, labelIdentifierFieldMetadataUniversalIdentifier, excludeLabelIdentifier = false, + startPosition = 0, }: { - flatApplication: FlatApplication; + applicationUniversalIdentifier: string; objectFlatFieldMetadatas: UniversalFlatFieldMetadata[]; viewUniversalIdentifier: string; labelIdentifierFieldMetadataUniversalIdentifier: string | null; excludeLabelIdentifier?: boolean; + startPosition?: number; }): UniversalFlatViewField[] => { const createdAt = new Date().toISOString(); - const defaultViewFields = objectFlatFieldMetadatas - .filter( + const defaultViewFields = orderFlatFieldMetadatasForSystemIndexView({ + labelIdentifierFieldMetadataUniversalIdentifier, + flatFieldMetadatas: objectFlatFieldMetadatas.filter( (field) => - field.name !== 'deletedAt' && - field.type !== FieldMetadataType.TS_VECTOR && - field.type !== FieldMetadataType.POSITION && - field.type !== FieldMetadataType.MORPH_RELATION && - field.type !== FieldMetadataType.RELATION && - // Include 'id' only if it's the label identifier (e.g., for junction tables) - (field.name !== 'id' || - field.universalIdentifier === - labelIdentifierFieldMetadataUniversalIdentifier) && + isFlatFieldMetadataDisplayableInDefaultView({ + flatFieldMetadata: field, + labelIdentifierFieldMetadataUniversalIdentifier, + }) && // Exclude label identifier field when requested (e.g., for FIELDS_WIDGET views) (!excludeLabelIdentifier || field.universalIdentifier !== labelIdentifierFieldMetadataUniversalIdentifier), - ) - .sort((a, b) => { - const aIsLabelIdentifierFieldMetadata = - a.universalIdentifier === - labelIdentifierFieldMetadataUniversalIdentifier; - const bIsLabelIdentifierFieldMetadata = - b.universalIdentifier === - labelIdentifierFieldMetadataUniversalIdentifier; - - if (aIsLabelIdentifierFieldMetadata && !bIsLabelIdentifierFieldMetadata) - return -1; - if (!aIsLabelIdentifierFieldMetadata && bIsLabelIdentifierFieldMetadata) - return 1; - - return 0; - }) - .map((field, index) => ({ - fieldMetadataUniversalIdentifier: field.universalIdentifier, + ), + }).map((field, index) => ({ + fieldMetadataUniversalIdentifier: field.universalIdentifier, + viewUniversalIdentifier, + viewFieldGroupUniversalIdentifier: null, + createdAt, + updatedAt: createdAt, + deletedAt: null, + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier, viewUniversalIdentifier, - viewFieldGroupUniversalIdentifier: null, - createdAt, - updatedAt: createdAt, - deletedAt: null, - universalIdentifier: v4(), - isVisible: true, - size: DEFAULT_VIEW_FIELD_SIZE, - position: index, - aggregateOperation: null, - isActive: true, - isSystemSideEffect: true, - universalOverrides: null, - applicationUniversalIdentifier: flatApplication.universalIdentifier, - })); + fieldMetadataUniversalIdentifier: field.universalIdentifier, + }), + isVisible: true, + size: DEFAULT_VIEW_FIELD_SIZE, + position: startPosition + index, + aggregateOperation: null, + isActive: true, + isSystemSideEffect: true, + universalOverrides: null, + applicationUniversalIdentifier, + })); return defaultViewFields; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util.ts new file mode 100644 index 0000000000..081550daaf --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/is-flat-field-metadata-displayable-in-default-view.util.ts @@ -0,0 +1,22 @@ +import { FieldMetadataType } from 'twenty-shared/types'; + +import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type'; + +export const isFlatFieldMetadataDisplayableInDefaultView = ({ + flatFieldMetadata, + labelIdentifierFieldMetadataUniversalIdentifier, +}: { + flatFieldMetadata: Pick< + UniversalFlatFieldMetadata, + 'name' | 'type' | 'universalIdentifier' + >; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; +}): boolean => + flatFieldMetadata.name !== 'deletedAt' && + flatFieldMetadata.type !== FieldMetadataType.TS_VECTOR && + flatFieldMetadata.type !== FieldMetadataType.POSITION && + flatFieldMetadata.type !== FieldMetadataType.MORPH_RELATION && + flatFieldMetadata.type !== FieldMetadataType.RELATION && + (flatFieldMetadata.name !== 'id' || + flatFieldMetadata.universalIdentifier === + labelIdentifierFieldMetadataUniversalIdentifier); diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util.ts new file mode 100644 index 0000000000..6318b9257a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/order-flat-field-metadatas-for-system-index-view.util.ts @@ -0,0 +1,20 @@ +export const orderFlatFieldMetadatasForSystemIndexView = < + TFlatFieldMetadata extends { universalIdentifier: string }, +>({ + flatFieldMetadatas, + labelIdentifierFieldMetadataUniversalIdentifier, +}: { + flatFieldMetadatas: TFlatFieldMetadata[]; + labelIdentifierFieldMetadataUniversalIdentifier: string | null; +}): TFlatFieldMetadata[] => [ + ...flatFieldMetadatas.filter( + (flatFieldMetadata) => + flatFieldMetadata.universalIdentifier === + labelIdentifierFieldMetadataUniversalIdentifier, + ), + ...flatFieldMetadatas.filter( + (flatFieldMetadata) => + flatFieldMetadata.universalIdentifier !== + labelIdentifierFieldMetadataUniversalIdentifier, + ), +]; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap index a1e08b37c0..72c102bd79 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap @@ -2493,54 +2493,48 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "id": "00000000-0000-0000-0000-000000000730", }, }, - "id": "00000000-0000-0000-0000-000000000760", + "id": "00000000-0000-0000-0000-000000000758", "views": { "allTimelineActivities": { - "id": "00000000-0000-0000-0000-000000000759", + "id": "00000000-0000-0000-0000-000000000757", "viewFieldGroups": {}, "viewFields": { "happensAt": { "id": "00000000-0000-0000-0000-000000000746", }, - "linkedRecordCachedName": { - "id": "00000000-0000-0000-0000-000000000749", - }, "name": { "id": "00000000-0000-0000-0000-000000000745", }, - "properties": { - "id": "00000000-0000-0000-0000-000000000747", - }, "targetCompany": { - "id": "00000000-0000-0000-0000-000000000751", + "id": "00000000-0000-0000-0000-000000000749", }, "targetDashboard": { - "id": "00000000-0000-0000-0000-000000000758", - }, - "targetNote": { - "id": "00000000-0000-0000-0000-000000000754", - }, - "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000752", - }, - "targetPerson": { - "id": "00000000-0000-0000-0000-000000000750", - }, - "targetTask": { - "id": "00000000-0000-0000-0000-000000000753", - }, - "targetWorkflow": { - "id": "00000000-0000-0000-0000-000000000755", - }, - "targetWorkflowRun": { - "id": "00000000-0000-0000-0000-000000000757", - }, - "targetWorkflowVersion": { "id": "00000000-0000-0000-0000-000000000756", }, - "workspaceMember": { + "targetNote": { + "id": "00000000-0000-0000-0000-000000000752", + }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000750", + }, + "targetPerson": { "id": "00000000-0000-0000-0000-000000000748", }, + "targetTask": { + "id": "00000000-0000-0000-0000-000000000751", + }, + "targetWorkflow": { + "id": "00000000-0000-0000-0000-000000000753", + }, + "targetWorkflowRun": { + "id": "00000000-0000-0000-0000-000000000755", + }, + "targetWorkflowVersion": { + "id": "00000000-0000-0000-0000-000000000754", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000747", + }, }, "viewGroups": {}, }, @@ -2549,80 +2543,80 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflow": { "fields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000777", - }, - "automatedTriggers": { "id": "00000000-0000-0000-0000-000000000775", }, - "coreWorkflowId": { - "id": "00000000-0000-0000-0000-000000000771", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000762", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000765", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000764", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000761", - }, - "lastPublishedVersionId": { - "id": "00000000-0000-0000-0000-000000000770", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000769", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000767", - }, - "runs": { - "id": "00000000-0000-0000-0000-000000000774", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000768", - }, - "statuses": { - "id": "00000000-0000-0000-0000-000000000772", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000776", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000763", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000766", - }, - "versions": { + "automatedTriggers": { "id": "00000000-0000-0000-0000-000000000773", }, + "coreWorkflowId": { + "id": "00000000-0000-0000-0000-000000000769", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000760", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000763", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000762", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000759", + }, + "lastPublishedVersionId": { + "id": "00000000-0000-0000-0000-000000000768", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000767", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000765", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000772", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000766", + }, + "statuses": { + "id": "00000000-0000-0000-0000-000000000770", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000774", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000761", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000764", + }, + "versions": { + "id": "00000000-0000-0000-0000-000000000771", + }, }, - "id": "00000000-0000-0000-0000-000000000785", + "id": "00000000-0000-0000-0000-000000000783", "views": { "allWorkflows": { - "id": "00000000-0000-0000-0000-000000000784", + "id": "00000000-0000-0000-0000-000000000782", "viewFieldGroups": {}, "viewFields": { "createdBy": { - "id": "00000000-0000-0000-0000-000000000781", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000778", - }, - "runs": { - "id": "00000000-0000-0000-0000-000000000783", - }, - "statuses": { "id": "00000000-0000-0000-0000-000000000779", }, + "name": { + "id": "00000000-0000-0000-0000-000000000776", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000781", + }, + "statuses": { + "id": "00000000-0000-0000-0000-000000000777", + }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000780", + "id": "00000000-0000-0000-0000-000000000778", }, "versions": { - "id": "00000000-0000-0000-0000-000000000782", + "id": "00000000-0000-0000-0000-000000000780", }, }, "viewGroups": {}, @@ -2632,80 +2626,80 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflowAutomatedTrigger": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000787", + "id": "00000000-0000-0000-0000-000000000785", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000790", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000789", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000786", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000792", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000793", - }, - "settings": { - "id": "00000000-0000-0000-0000-000000000795", - }, - "type": { - "id": "00000000-0000-0000-0000-000000000794", - }, - "updatedAt": { "id": "00000000-0000-0000-0000-000000000788", }, - "updatedBy": { + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000787", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000784", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000790", + }, + "searchVector": { "id": "00000000-0000-0000-0000-000000000791", }, + "settings": { + "id": "00000000-0000-0000-0000-000000000793", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000792", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000786", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000789", + }, "workflow": { - "id": "00000000-0000-0000-0000-000000000796", + "id": "00000000-0000-0000-0000-000000000794", }, }, - "id": "00000000-0000-0000-0000-000000000808", + "id": "00000000-0000-0000-0000-000000000806", "views": { "allWorkflowAutomatedTriggers": { - "id": "00000000-0000-0000-0000-000000000800", + "id": "00000000-0000-0000-0000-000000000798", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000799", - }, - "type": { "id": "00000000-0000-0000-0000-000000000797", }, + "type": { + "id": "00000000-0000-0000-0000-000000000795", + }, "workflow": { - "id": "00000000-0000-0000-0000-000000000798", + "id": "00000000-0000-0000-0000-000000000796", }, }, "viewGroups": {}, }, "workflowAutomatedTriggerRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000807", + "id": "00000000-0000-0000-0000-000000000805", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000805", + "id": "00000000-0000-0000-0000-000000000803", }, "system": { - "id": "00000000-0000-0000-0000-000000000806", + "id": "00000000-0000-0000-0000-000000000804", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000803", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000804", - }, - "type": { "id": "00000000-0000-0000-0000-000000000801", }, - "workflow": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000802", }, + "type": { + "id": "00000000-0000-0000-0000-000000000799", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000800", + }, }, "viewGroups": {}, }, @@ -2714,134 +2708,125 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflowRun": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000810", + "id": "00000000-0000-0000-0000-000000000808", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000813", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000812", - }, - "endedAt": { - "id": "00000000-0000-0000-0000-000000000822", - }, - "enqueuedAt": { - "id": "00000000-0000-0000-0000-000000000820", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000809", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000817", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000815", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000816", - }, - "startedAt": { - "id": "00000000-0000-0000-0000-000000000821", - }, - "state": { - "id": "00000000-0000-0000-0000-000000000824", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000823", - }, - "stepLogs": { - "id": "00000000-0000-0000-0000-000000000825", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000826", - }, - "updatedAt": { "id": "00000000-0000-0000-0000-000000000811", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000814", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000810", }, - "workflow": { - "id": "00000000-0000-0000-0000-000000000819", + "endedAt": { + "id": "00000000-0000-0000-0000-000000000820", }, - "workflowVersion": { + "enqueuedAt": { "id": "00000000-0000-0000-0000-000000000818", }, + "id": { + "id": "00000000-0000-0000-0000-000000000807", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000815", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000813", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000814", + }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000819", + }, + "state": { + "id": "00000000-0000-0000-0000-000000000822", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000821", + }, + "stepLogs": { + "id": "00000000-0000-0000-0000-000000000823", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000824", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000809", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000812", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000817", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000816", + }, }, - "id": "00000000-0000-0000-0000-000000000849", + "id": "00000000-0000-0000-0000-000000000844", "views": { "allWorkflowRuns": { - "id": "00000000-0000-0000-0000-000000000833", + "id": "00000000-0000-0000-0000-000000000828", "viewFieldGroups": {}, "viewFields": { - "createdBy": { - "id": "00000000-0000-0000-0000-000000000831", - }, "name": { - "id": "00000000-0000-0000-0000-000000000827", - }, - "startedAt": { - "id": "00000000-0000-0000-0000-000000000830", + "id": "00000000-0000-0000-0000-000000000825", }, "status": { - "id": "00000000-0000-0000-0000-000000000829", + "id": "00000000-0000-0000-0000-000000000827", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000828", - }, - "workflowVersion": { - "id": "00000000-0000-0000-0000-000000000832", + "id": "00000000-0000-0000-0000-000000000826", }, }, "viewGroups": {}, }, "workflowRunRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000848", + "id": "00000000-0000-0000-0000-000000000843", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000846", + "id": "00000000-0000-0000-0000-000000000841", }, "system": { - "id": "00000000-0000-0000-0000-000000000847", + "id": "00000000-0000-0000-0000-000000000842", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000839", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000840", - }, - "endedAt": { - "id": "00000000-0000-0000-0000-000000000838", - }, - "enqueuedAt": { - "id": "00000000-0000-0000-0000-000000000841", - }, - "startedAt": { - "id": "00000000-0000-0000-0000-000000000837", - }, - "state": { - "id": "00000000-0000-0000-0000-000000000842", - }, - "status": { "id": "00000000-0000-0000-0000-000000000834", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000845", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000843", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000844", - }, - "workflow": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000835", }, - "workflowVersion": { + "endedAt": { + "id": "00000000-0000-0000-0000-000000000833", + }, + "enqueuedAt": { "id": "00000000-0000-0000-0000-000000000836", }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000832", + }, + "state": { + "id": "00000000-0000-0000-0000-000000000837", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000829", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000840", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000838", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000839", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000830", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000831", + }, }, "viewGroups": {}, }, @@ -2850,118 +2835,118 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflowVersion": { "fields": { "coreWorkflowVersionId": { - "id": "00000000-0000-0000-0000-000000000864", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000851", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000854", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000853", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000850", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000858", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000856", - }, - "runs": { - "id": "00000000-0000-0000-0000-000000000862", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000857", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000861", - }, - "steps": { - "id": "00000000-0000-0000-0000-000000000863", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000865", - }, - "trigger": { - "id": "00000000-0000-0000-0000-000000000860", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000852", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000855", - }, - "workflow": { "id": "00000000-0000-0000-0000-000000000859", }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000846", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000849", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000848", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000845", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000853", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000851", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000857", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000852", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000856", + }, + "steps": { + "id": "00000000-0000-0000-0000-000000000858", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000860", + }, + "trigger": { + "id": "00000000-0000-0000-0000-000000000855", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000847", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000850", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000854", + }, }, - "id": "00000000-0000-0000-0000-000000000885", + "id": "00000000-0000-0000-0000-000000000880", "views": { "allWorkflowVersions": { - "id": "00000000-0000-0000-0000-000000000871", + "id": "00000000-0000-0000-0000-000000000866", "viewFieldGroups": {}, "viewFields": { "name": { - "id": "00000000-0000-0000-0000-000000000866", + "id": "00000000-0000-0000-0000-000000000861", }, "runs": { - "id": "00000000-0000-0000-0000-000000000870", + "id": "00000000-0000-0000-0000-000000000865", }, "status": { - "id": "00000000-0000-0000-0000-000000000868", + "id": "00000000-0000-0000-0000-000000000863", }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000869", + "id": "00000000-0000-0000-0000-000000000864", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000867", + "id": "00000000-0000-0000-0000-000000000862", }, }, "viewGroups": {}, }, "workflowVersionRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000884", + "id": "00000000-0000-0000-0000-000000000879", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000882", + "id": "00000000-0000-0000-0000-000000000877", }, "system": { - "id": "00000000-0000-0000-0000-000000000883", + "id": "00000000-0000-0000-0000-000000000878", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000875", + "id": "00000000-0000-0000-0000-000000000870", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000877", - }, - "runs": { - "id": "00000000-0000-0000-0000-000000000880", - }, - "status": { "id": "00000000-0000-0000-0000-000000000872", }, + "runs": { + "id": "00000000-0000-0000-0000-000000000875", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000867", + }, "steps": { - "id": "00000000-0000-0000-0000-000000000876", + "id": "00000000-0000-0000-0000-000000000871", }, "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000881", + "id": "00000000-0000-0000-0000-000000000876", }, "trigger": { - "id": "00000000-0000-0000-0000-000000000874", + "id": "00000000-0000-0000-0000-000000000869", }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000878", + "id": "00000000-0000-0000-0000-000000000873", }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000879", + "id": "00000000-0000-0000-0000-000000000874", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000873", + "id": "00000000-0000-0000-0000-000000000868", }, }, "viewGroups": {}, @@ -2971,125 +2956,104 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workspaceMember": { "fields": { "accountOwnerForCompanies": { - "id": "00000000-0000-0000-0000-000000000903", - }, - "assignedTasks": { - "id": "00000000-0000-0000-0000-000000000901", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000000897", - }, - "blocklist": { - "id": "00000000-0000-0000-0000-000000000905", - }, - "calendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000000906", - }, - "calendarStartDay": { - "id": "00000000-0000-0000-0000-000000000911", - }, - "colorScheme": { - "id": "00000000-0000-0000-0000-000000000895", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000887", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000890", - }, - "dateFormat": { - "id": "00000000-0000-0000-0000-000000000909", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000889", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000886", - }, - "jobTitle": { - "id": "00000000-0000-0000-0000-000000000899", - }, - "locale": { - "id": "00000000-0000-0000-0000-000000000896", - }, - "messageParticipants": { - "id": "00000000-0000-0000-0000-000000000904", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000894", - }, - "numberFormat": { - "id": "00000000-0000-0000-0000-000000000912", - }, - "ownedOpportunities": { - "id": "00000000-0000-0000-0000-000000000902", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000892", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000893", - }, - "timeFormat": { - "id": "00000000-0000-0000-0000-000000000910", - }, - "timeZone": { - "id": "00000000-0000-0000-0000-000000000908", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000907", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000888", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000891", - }, - "userEmail": { "id": "00000000-0000-0000-0000-000000000898", }, - "userId": { + "assignedTasks": { + "id": "00000000-0000-0000-0000-000000000896", + }, + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000892", + }, + "blocklist": { "id": "00000000-0000-0000-0000-000000000900", }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000901", + }, + "calendarStartDay": { + "id": "00000000-0000-0000-0000-000000000906", + }, + "colorScheme": { + "id": "00000000-0000-0000-0000-000000000890", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000882", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000885", + }, + "dateFormat": { + "id": "00000000-0000-0000-0000-000000000904", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000884", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000881", + }, + "jobTitle": { + "id": "00000000-0000-0000-0000-000000000894", + }, + "locale": { + "id": "00000000-0000-0000-0000-000000000891", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000899", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000889", + }, + "numberFormat": { + "id": "00000000-0000-0000-0000-000000000907", + }, + "ownedOpportunities": { + "id": "00000000-0000-0000-0000-000000000897", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000887", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000888", + }, + "timeFormat": { + "id": "00000000-0000-0000-0000-000000000905", + }, + "timeZone": { + "id": "00000000-0000-0000-0000-000000000903", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000902", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000883", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000886", + }, + "userEmail": { + "id": "00000000-0000-0000-0000-000000000893", + }, + "userId": { + "id": "00000000-0000-0000-0000-000000000895", + }, }, - "id": "00000000-0000-0000-0000-000000000925", + "id": "00000000-0000-0000-0000-000000000913", "views": { "allWorkspaceMembers": { - "id": "00000000-0000-0000-0000-000000000924", + "id": "00000000-0000-0000-0000-000000000912", "viewFieldGroups": {}, "viewFields": { "assignedTasks": { - "id": "00000000-0000-0000-0000-000000000923", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000000915", - }, - "colorScheme": { - "id": "00000000-0000-0000-0000-000000000916", + "id": "00000000-0000-0000-0000-000000000911", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000921", - }, - "dateFormat": { - "id": "00000000-0000-0000-0000-000000000919", - }, - "locale": { - "id": "00000000-0000-0000-0000-000000000917", + "id": "00000000-0000-0000-0000-000000000909", }, "name": { - "id": "00000000-0000-0000-0000-000000000913", + "id": "00000000-0000-0000-0000-000000000908", }, "ownedOpportunities": { - "id": "00000000-0000-0000-0000-000000000922", - }, - "timeFormat": { - "id": "00000000-0000-0000-0000-000000000920", - }, - "timeZone": { - "id": "00000000-0000-0000-0000-000000000918", - }, - "userEmail": { - "id": "00000000-0000-0000-0000-000000000914", + "id": "00000000-0000-0000-0000-000000000910", }, }, "viewGroups": {}, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts index 63eaa130d8..e64599d0b9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts @@ -1,17 +1,21 @@ -import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; +import { v4 } from 'uuid'; -let uuidCounter = 0; +import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util'; jest.mock('uuid', () => ({ ...jest.requireActual('uuid'), - v4: jest.fn( - () => `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`, - ), + v4: jest.fn(), })); describe('getStandardObjectMetadataRelatedEntityIds', () => { + let uuidCounter = 0; + beforeEach(() => { uuidCounter = 0; + (v4 as jest.Mock).mockImplementation( + () => + `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`, + ); }); afterAll(() => { diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util.ts index f8a6caf48c..0eca580981 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util.ts @@ -79,8 +79,6 @@ export type BuildStandardFlatViewFieldMetadataMapsArgs = Omit< export const buildStandardFlatViewFieldMetadataMaps = ( args: BuildStandardFlatViewFieldMetadataMapsArgs, ): FlatEntityMaps => { - const { flatViewMaps } = args.dependencyFlatEntityMaps; - const allViewFieldMetadatas: FlatViewField[] = ( Object.keys( STANDARD_FLAT_VIEW_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME, @@ -100,16 +98,8 @@ export const buildStandardFlatViewFieldMetadataMaps = ( let flatViewFieldMaps = createEmptyFlatEntityMaps(); for (const viewFieldMetadata of allViewFieldMetadatas) { - const parentView = - flatViewMaps.byUniversalIdentifier[ - viewFieldMetadata.viewUniversalIdentifier - ]; - flatViewFieldMaps = addFlatEntityToFlatEntityMapsOrThrow({ - flatEntity: { - ...viewFieldMetadata, - isSystemSideEffect: parentView?.isSystemSideEffect ?? false, - }, + flatEntity: viewFieldMetadata, flatEntityMaps: flatViewFieldMaps, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts index 41c1b99a97..071857c41f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/create-standard-view-field-flat-metadata.util.ts @@ -1,7 +1,7 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; +import { type AggregateOperations } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; -import { type AggregateOperations } from 'twenty-shared/types'; import { type FlatViewField } from 'src/engine/metadata-modules/flat-view-field/types/flat-view-field.type'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; @@ -51,6 +51,7 @@ export const createStandardViewFieldFlatMetadata = < viewFieldGroupName = null, }, standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, twentyStandardApplicationId, now, }: CreateStandardViewFieldArgs): FlatViewField => { @@ -79,6 +80,17 @@ export const createStandardViewFieldFlatMetadata = < ); } + const parentView = + dependencyFlatEntityMaps.flatViewMaps.byUniversalIdentifier[ + viewDefinition.universalIdentifier + ]; + + if (!isDefined(parentView)) { + throw new Error( + `Missing parent view ${objectName} ${viewName.toString()} for view field ${viewFieldName}`, + ); + } + let viewFieldGroupId: string | null = null; let viewFieldGroupUniversalIdentifier: string | null = null; @@ -121,7 +133,7 @@ export const createStandardViewFieldFlatMetadata = < size, aggregateOperation, isActive: true, - isSystemSideEffect: false, + isSystemSideEffect: parentView.isSystemSideEffect, overrides: null, universalOverrides: null, createdAt: now, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type.ts index 53dca31d87..20d052f4b6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/metadata-universal-flat-entity-properties-to-compare.type.ts @@ -54,7 +54,6 @@ type Assertions = [ | 'deletedAt' | 'type' | 'position' - | 'key' | 'isCompact' | 'openRecordIn' | 'kanbanAggregateOperation' diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-view-validator.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-view-validator.service.spec.ts new file mode 100644 index 0000000000..c85e794c30 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-view-validator.service.spec.ts @@ -0,0 +1,186 @@ +import { ViewKey, ViewType } from 'twenty-shared/types'; + +import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service'; + +const OBJECT_UNIVERSAL_IDENTIFIER = '00000000-0000-4000-8000-0000000000b1'; +const VIEW_UNIVERSAL_IDENTIFIER = '00000000-0000-4000-8000-0000000000c1'; +const OTHER_VIEW_UNIVERSAL_IDENTIFIER = '00000000-0000-4000-8000-0000000000c2'; + +type TestFlatView = { + universalIdentifier: string; + objectMetadataUniversalIdentifier: string; + key: ViewKey | null; + type: ViewType; + isSystemSideEffect: boolean; + deletedAt: string | null; +}; + +const buildFlatView = ( + overrides: Partial = {}, +): TestFlatView => ({ + universalIdentifier: VIEW_UNIVERSAL_IDENTIFIER, + objectMetadataUniversalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + key: null, + type: ViewType.TABLE, + isSystemSideEffect: false, + deletedAt: null, + ...overrides, +}); + +const mapsFrom = ( + entities: { universalIdentifier: string; [key: string]: unknown }[], +) => ({ + byUniversalIdentifier: Object.fromEntries( + entities.map((entity) => [entity.universalIdentifier, entity]), + ), +}); + +const buildCreationArgs = ({ + flatViewToValidate, + existingFlatViews = [], +}: { + flatViewToValidate: TestFlatView; + existingFlatViews?: TestFlatView[]; +}) => + ({ + flatEntityToValidate: flatViewToValidate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatViewMaps: mapsFrom(existingFlatViews), + flatFieldMetadataMaps: mapsFrom([]), + flatObjectMetadataMaps: mapsFrom([ + { + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifiers: existingFlatViews.map( + (existingFlatView) => existingFlatView.universalIdentifier, + ), + }, + ]), + }, + additionalCacheDataMaps: { featureFlagsMap: {} }, + }) as unknown as Parameters< + FlatViewValidatorService['validateFlatViewCreation'] + >[0]; + +const buildUpdateArgs = ({ + flatEntityUpdate, + existingFlatView, +}: { + flatEntityUpdate: Record; + existingFlatView: TestFlatView; +}) => + ({ + universalIdentifier: existingFlatView.universalIdentifier, + flatEntityUpdate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatViewMaps: mapsFrom([existingFlatView]), + flatFieldMetadataMaps: mapsFrom([]), + }, + additionalCacheDataMaps: { featureFlagsMap: {} }, + }) as unknown as Parameters< + FlatViewValidatorService['validateFlatViewUpdate'] + >[0]; + +describe('FlatViewValidatorService INDEX key reservation', () => { + let service: FlatViewValidatorService; + + beforeEach(() => { + service = new FlatViewValidatorService(); + }); + + describe('creation', () => { + it('rejects a caller-provided INDEX view', () => { + const result = service.validateFlatViewCreation( + buildCreationArgs({ + flatViewToValidate: buildFlatView({ + key: ViewKey.INDEX, + isSystemSideEffect: false, + }), + }), + ); + + expect(result.errors).toHaveLength(1); + expect(result.errors[0].message).toContain('reserved'); + }); + + it('accepts an engine-emitted INDEX view', () => { + const result = service.validateFlatViewCreation( + buildCreationArgs({ + flatViewToValidate: buildFlatView({ + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + }), + ); + + expect(result.errors).toEqual([]); + }); + + it('rejects a second INDEX view on the same object', () => { + const result = service.validateFlatViewCreation( + buildCreationArgs({ + flatViewToValidate: buildFlatView({ + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + existingFlatViews: [ + buildFlatView({ + universalIdentifier: OTHER_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + ], + }), + ); + + expect(result.errors).toHaveLength(1); + expect(result.errors[0].message).toContain('already has an INDEX view'); + }); + + it('ignores a soft-deleted INDEX view when checking the singleton', () => { + const result = service.validateFlatViewCreation( + buildCreationArgs({ + flatViewToValidate: buildFlatView({ + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + existingFlatViews: [ + buildFlatView({ + universalIdentifier: OTHER_VIEW_UNIVERSAL_IDENTIFIER, + key: ViewKey.INDEX, + isSystemSideEffect: true, + deletedAt: '2024-01-01T00:00:00.000Z', + }), + ], + }), + ); + + expect(result.errors).toEqual([]); + }); + + it('accepts a caller-provided view without a key', () => { + const result = service.validateFlatViewCreation( + buildCreationArgs({ + flatViewToValidate: buildFlatView({ key: null }), + }), + ); + + expect(result.errors).toEqual([]); + }); + }); + + describe('update', () => { + it('accepts an update on an INDEX view', () => { + const result = service.validateFlatViewUpdate( + buildUpdateArgs({ + flatEntityUpdate: { name: 'Renamed default view' }, + existingFlatView: buildFlatView({ + key: ViewKey.INDEX, + isSystemSideEffect: true, + }), + }), + ); + + expect(result.errors).toEqual([]); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service.ts index 31fae3fc84..0e5c6baec3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-view-validator.service.ts @@ -5,6 +5,7 @@ import { FieldMetadataType, RelationType, ViewCalendarLayout, + ViewKey, ViewType, } from 'twenty-shared/types'; import { getViewLayoutFromViewType, isDefined } from 'twenty-shared/utils'; @@ -459,6 +460,41 @@ export class FlatViewValidatorService { }); } + if (flatViewToValidate.key === ViewKey.INDEX) { + if (flatViewToValidate.isSystemSideEffect !== true) { + validationResult.errors.push({ + code: ViewExceptionCode.INVALID_VIEW_DATA, + message: t`The INDEX view key is reserved for the engine-owned default view; remove the key from the view definition`, + userFriendlyMessage: msg`The INDEX view key is reserved for the default view`, + }); + } + + const objectAlreadyHasIndexFlatView = + isDefined(optimisticFlatObjectMetadata) && + optimisticFlatObjectMetadata.viewUniversalIdentifiers.some( + (viewUniversalIdentifier) => { + const flatView = findFlatEntityByUniversalIdentifier({ + universalIdentifier: viewUniversalIdentifier, + flatEntityMaps: optimisticFlatViewMaps, + }); + + return ( + isDefined(flatView) && + flatView.key === ViewKey.INDEX && + !isDefined(flatView.deletedAt) + ); + }, + ); + + if (objectAlreadyHasIndexFlatView) { + validationResult.errors.push({ + code: ViewExceptionCode.INVALID_VIEW_DATA, + message: t`Object already has an INDEX view`, + userFriendlyMessage: msg`This object already has a default view`, + }); + } + } + if ( isDefined( flatViewToValidate.kanbanAggregateOperationFieldMetadataUniversalIdentifier, diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-manifest-update-view-field.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-manifest-update-view-field.integration-spec.ts.snap new file mode 100644 index 0000000000..6ec91ce174 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-manifest-update-view-field.integration-spec.ts.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Failing manifest update - standalone view fields on existing views rejects a standalone view field whose target view does not exist 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "viewField": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + ], + }, + "message": "Validation failed for 1 viewField", + "summary": { + "totalErrors": 1, + "viewField": 1, + }, + "userFriendlyMessage": "View not found", + }, + "message": "Validation errors occurred while syncing application manifest metadata", + "name": "GraphQLError", +} +`; + +exports[`Failing manifest update - standalone view fields on existing views rejects two standalone view fields targeting the same field on the same view 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "viewField": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "View field with same fieldMetadataUniversalIdentifier and viewUniversalIdentifier already exists", + "userFriendlyMessage": "View field already exists", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + ], + }, + "message": "Validation failed for 1 viewField", + "summary": { + "totalErrors": 1, + "viewField": 1, + }, + "userFriendlyMessage": "View field already exists", + }, + "message": "Validation errors occurred while syncing application manifest metadata", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-search-vector-identifier-hijack.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-search-vector-identifier-hijack.integration-spec.ts.snap index ef831e9f3b..4e63c9954e 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-search-vector-identifier-hijack.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/failing-sync-application-search-vector-identifier-hijack.integration-spec.ts.snap @@ -62,13 +62,33 @@ exports[`Sync application should reject hijacking the searchVector system field "type": "create", }, ], + "viewField": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + ], }, - "message": "Validation failed for 1 fieldMetadata", + "message": "Validation failed for 1 fieldMetadata, 1 viewField", "summary": { "fieldMetadata": 1, - "totalErrors": 1, + "totalErrors": 2, + "viewField": 1, }, - "userFriendlyMessage": "Name "searchVector" is not available as it is already used by another field", + "userFriendlyMessage": "Many validation errors", }, "message": "Validation errors occurred while syncing application manifest metadata", "name": "GraphQLError", diff --git a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap index 0c5d8f4d09..253415c041 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/application/__snapshots__/successful-sync-application-workspace-migration.integration-spec.ts.snap @@ -35,6 +35,27 @@ exports[`syncApplication should create a TEXT field on the standard Company obje "metadataName": "fieldMetadata", "type": "create", }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": false, + "position": 14, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, { "flatEntity": { "applicationUniversalIdentifier": Any, @@ -738,6 +759,168 @@ exports[`syncApplication should delete old field and create equivalent one when "metadataName": "index", "type": "create", }, + { + "flatEntity": { + "anyFieldFilterValue": null, + "applicationUniversalIdentifier": Any, + "calendarEndFieldMetadataUniversalIdentifier": null, + "calendarFieldMetadataUniversalIdentifier": null, + "calendarLayout": null, + "createdAt": Any, + "createdByUserWorkspaceId": null, + "deletedAt": null, + "icon": "IconList", + "id": Any, + "isActive": true, + "isCompact": false, + "isCustom": true, + "isSystemSideEffect": true, + "kanbanAggregateOperation": null, + "kanbanAggregateOperationFieldMetadataUniversalIdentifier": null, + "kanbanColumnWidth": null, + "key": "INDEX", + "mainGroupByFieldMetadataUniversalIdentifier": null, + "name": "All {objectLabelPlural}", + "objectMetadataUniversalIdentifier": Any, + "openRecordIn": "SIDE_PANEL", + "position": 0, + "shouldHideEmptyGroups": false, + "type": "TABLE", + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "visibility": "WORKSPACE", + }, + "id": Any, + "metadataName": "view", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 1, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 0, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 2, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 3, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 4, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 5, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, { "flatEntity": { "applicationUniversalIdentifier": Any, @@ -803,7 +986,9 @@ exports[`syncApplication should delete old field and create equivalent one when "universalIdentifier": Any, "universalSettings": null, "updatedAt": Any, - "viewFieldUniversalIdentifiers": [], + "viewFieldUniversalIdentifiers": [ + "da099a9e-dfb5-5eb3-bc29-e7a789e4b765", + ], "viewFilterUniversalIdentifiers": [], "viewSortUniversalIdentifiers": [], }, @@ -842,6 +1027,49 @@ exports[`syncApplication should delete old field and create equivalent one when "metadataName": "fieldMetadata", "type": "create", }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 1, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "delete", + "universalIdentifier": Any, + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": false, + "position": 6, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, ], "applicationUniversalIdentifier": Any, }, @@ -1522,6 +1750,168 @@ exports[`syncApplication should return workspace migration actions on initial sy "metadataName": "index", "type": "create", }, + { + "flatEntity": { + "anyFieldFilterValue": null, + "applicationUniversalIdentifier": Any, + "calendarEndFieldMetadataUniversalIdentifier": null, + "calendarFieldMetadataUniversalIdentifier": null, + "calendarLayout": null, + "createdAt": Any, + "createdByUserWorkspaceId": null, + "deletedAt": null, + "icon": "IconList", + "id": Any, + "isActive": true, + "isCompact": false, + "isCustom": true, + "isSystemSideEffect": true, + "kanbanAggregateOperation": null, + "kanbanAggregateOperationFieldMetadataUniversalIdentifier": null, + "kanbanColumnWidth": null, + "key": "INDEX", + "mainGroupByFieldMetadataUniversalIdentifier": null, + "name": "All {objectLabelPlural}", + "objectMetadataUniversalIdentifier": Any, + "openRecordIn": "SIDE_PANEL", + "position": 0, + "shouldHideEmptyGroups": false, + "type": "TABLE", + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "visibility": "WORKSPACE", + }, + "id": Any, + "metadataName": "view", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 1, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 0, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 2, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 3, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 4, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, + { + "flatEntity": { + "aggregateOperation": null, + "applicationUniversalIdentifier": Any, + "createdAt": Any, + "deletedAt": null, + "fieldMetadataUniversalIdentifier": Any, + "isActive": true, + "isSystemSideEffect": true, + "isVisible": true, + "position": 5, + "size": 180, + "universalIdentifier": Any, + "universalOverrides": null, + "updatedAt": Any, + "viewFieldGroupUniversalIdentifier": null, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "type": "create", + }, { "flatEntity": { "applicationUniversalIdentifier": Any, diff --git a/packages/twenty-server/test/integration/metadata/suites/application/failing-manifest-update-view-field.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/failing-manifest-update-view-field.integration-spec.ts new file mode 100644 index 0000000000..5bef48a05a --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/application/failing-manifest-update-view-field.integration-spec.ts @@ -0,0 +1,114 @@ +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util'; +import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util'; +import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util'; +import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util'; +import { type FieldManifest, type Manifest } from 'twenty-shared/application'; +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { v4 as uuidv4 } from 'uuid'; + +const TEST_APP_ID = uuidv4(); +const TEST_ROLE_ID = uuidv4(); +const TEST_FIELD_ID = uuidv4(); +const TEST_VIEW_FIELD_ID = uuidv4(); +const TEST_SECOND_VIEW_FIELD_ID = uuidv4(); + +const PERSON_OBJECT_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.person.universalIdentifier; +const PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.person.views.personRecordPageFields.universalIdentifier; +const PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.person.views.personRecordPageFields.viewFieldGroups.general + .universalIdentifier; + +const CUSTOM_FIELD_NAME = 'integrationContributedColumn'; + +const personFieldManifest: FieldManifest = { + universalIdentifier: TEST_FIELD_ID, + type: FieldMetadataType.TEXT, + name: CUSTOM_FIELD_NAME, + label: 'Integration Contributed Column', + description: 'Custom field contributed to the standard Person object', + icon: 'IconStar', + objectUniversalIdentifier: PERSON_OBJECT_UNIVERSAL_IDENTIFIER, +}; + +const buildManifest = ( + overrides?: Partial>, +) => + buildBaseManifest({ + appId: TEST_APP_ID, + roleId: TEST_ROLE_ID, + overrides, + }); + +describe('Failing manifest update - standalone view fields on existing views', () => { + beforeEach(async () => { + await setupApplicationForSync({ + applicationUniversalIdentifier: TEST_APP_ID, + name: 'Test Application', + description: 'App for testing standalone view field manifest updates', + sourcePath: 'test-manifest-update-view-field', + }); + }, 60000); + + afterEach(async () => { + await cleanupApplicationAndAppRegistration({ + applicationUniversalIdentifier: TEST_APP_ID, + }); + }); + + it('rejects two standalone view fields targeting the same field on the same view', async () => { + const { errors } = await syncApplication({ + manifest: buildManifest({ + fields: [personFieldManifest], + viewFields: [ + { + universalIdentifier: TEST_VIEW_FIELD_ID, + viewUniversalIdentifier: + PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER, + viewFieldGroupUniversalIdentifier: + PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: TEST_FIELD_ID, + position: 10, + isVisible: true, + }, + { + universalIdentifier: TEST_SECOND_VIEW_FIELD_ID, + viewUniversalIdentifier: + PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER, + viewFieldGroupUniversalIdentifier: + PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER, + fieldMetadataUniversalIdentifier: TEST_FIELD_ID, + position: 11, + isVisible: true, + }, + ], + }), + expectToFail: true, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }, 60000); + + it('rejects a standalone view field whose target view does not exist', async () => { + const { errors } = await syncApplication({ + manifest: buildManifest({ + fields: [personFieldManifest], + viewFields: [ + { + universalIdentifier: TEST_VIEW_FIELD_ID, + viewUniversalIdentifier: uuidv4(), + fieldMetadataUniversalIdentifier: TEST_FIELD_ID, + position: 10, + isVisible: true, + }, + ], + }), + expectToFail: true, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }, 60000); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/relabel-onto-new-field-manifest-sync.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/relabel-onto-new-field-manifest-sync.integration-spec.ts index def6428087..867342b1ef 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/relabel-onto-new-field-manifest-sync.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/relabel-onto-new-field-manifest-sync.integration-spec.ts @@ -1,11 +1,14 @@ +import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants'; import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util'; import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/application/utils/build-default-object-manifest.util'; import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util'; import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util'; import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util'; import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util'; +import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util'; +import { findViews } from 'test/integration/metadata/suites/view/utils/find-views.util'; import { type ObjectManifest } from 'twenty-shared/application'; -import { FieldMetadataType } from 'twenty-shared/types'; +import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 as uuidv4 } from 'uuid'; @@ -80,6 +83,54 @@ const findTicketObject = async () => { ); }; +// The label identifier view field on the INDEX view must be visible and +// strictly lowest, whatever provisioned it (fieldIndexViewFieldOnCreate on +// creation, objectIndexViewLabelIdentifierOnUpdate on relabel). +const expectLabelIdentifierIndexViewFieldVisibleAndLowest = async ({ + objectMetadataId, + labelFieldMetadataId, +}: { + objectMetadataId: string; + labelFieldMetadataId: string; +}) => { + const { data: viewsData } = await findViews({ + objectMetadataId, + gqlFields: 'id key', + expectToFail: false, + }); + + const indexView = viewsData?.getViews.find( + (view) => view.key === ViewKey.INDEX, + ); + + if (!isDefined(indexView)) { + throw new Error('expected an INDEX view for the ticket object'); + } + + const { data: viewFieldsData } = await findViewFields({ + viewId: indexView.id, + gqlFields: VIEW_FIELD_GQL_FIELDS, + expectToFail: false, + }); + + const viewFields = viewFieldsData?.getViewFields ?? []; + const labelViewField = viewFields.find( + (viewField) => viewField.fieldMetadataId === labelFieldMetadataId, + ); + + if (!isDefined(labelViewField)) { + throw new Error('expected an INDEX view field for the label identifier'); + } + + expect(labelViewField.isVisible).toBe(true); + + for (const viewField of viewFields) { + if (viewField.id !== labelViewField.id) { + expect(labelViewField.position).toBeLessThan(viewField.position); + } + } +}; + describe('Manifest sync - relabel label identifier onto a newly introduced field', () => { beforeEach(async () => { await setupApplicationForSync({ @@ -120,6 +171,17 @@ describe('Manifest sync - relabel label identifier onto a newly introduced field nameField?.id, ); + // Object created with a custom label identifier backed by a field created + // in the same sync: its INDEX view field is visible and strictly lowest. + if (!isDefined(ticketAfterFirstSync) || !isDefined(nameField)) { + throw new Error('expected the ticket object and its name field'); + } + + await expectLabelIdentifierIndexViewFieldVisibleAndLowest({ + objectMetadataId: ticketAfterFirstSync.id, + labelFieldMetadataId: nameField.id, + }); + await syncApplication({ manifest: buildManifest( buildTicketObject({ @@ -139,6 +201,15 @@ describe('Manifest sync - relabel label identifier onto a newly introduced field expect(ticketAfterSecondSync?.labelIdentifierFieldMetadataId).toBe( codeField?.id, ); + + if (!isDefined(ticketAfterSecondSync) || !isDefined(codeField)) { + throw new Error('expected the ticket object and its code field'); + } + + await expectLabelIdentifierIndexViewFieldVisibleAndLowest({ + objectMetadataId: ticketAfterSecondSync.id, + labelFieldMetadataId: codeField.id, + }); }, 60000); it('should relabel onto a newly introduced field when introduction and relabel are split across two syncs, exposing the enriched labelIdentifierFieldMetadataId through the metadata API', async () => { diff --git a/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-view-field.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-view-field.integration-spec.ts index c141573610..413f34dae6 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-view-field.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-view-field.integration-spec.ts @@ -1,3 +1,4 @@ +import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants'; import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util'; import { cleanupApplicationAndAppRegistration } from 'test/integration/metadata/suites/application/utils/cleanup-application-and-app-registration.util'; import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util'; @@ -6,23 +7,23 @@ import { uninstallApplication } from 'test/integration/metadata/suites/applicati import { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util'; import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util'; import { findViews } from 'test/integration/metadata/suites/view/utils/find-views.util'; -import { VIEW_FIELD_GQL_FIELDS } from 'test/integration/constants/view-gql-fields.constants'; -import type { FieldManifest } from 'twenty-shared/application'; -import { type Manifest } from 'twenty-shared/application'; +import { type FieldManifest, type Manifest } from 'twenty-shared/application'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; -import { FieldMetadataType, ViewKey } from 'twenty-shared/types'; +import { FieldMetadataType, ViewType } from 'twenty-shared/types'; import { v4 as uuidv4 } from 'uuid'; const TEST_APP_ID = uuidv4(); const TEST_ROLE_ID = uuidv4(); const TEST_FIELD_ID = uuidv4(); const TEST_VIEW_FIELD_ID = uuidv4(); -const TEST_SECOND_VIEW_FIELD_ID = uuidv4(); const PERSON_OBJECT_UNIVERSAL_IDENTIFIER = STANDARD_OBJECTS.person.universalIdentifier; -const ALL_PEOPLE_VIEW_UNIVERSAL_IDENTIFIER = - STANDARD_OBJECTS.person.views.allPeople.universalIdentifier; +const PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.person.views.personRecordPageFields.universalIdentifier; +const PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.person.views.personRecordPageFields.viewFieldGroups.general + .universalIdentifier; const CUSTOM_FIELD_NAME = 'integrationContributedColumn'; @@ -62,25 +63,25 @@ const findPersonObject = async () => { return person; }; -const findAllPeopleViewId = async (personObjectId: string) => { +const findRecordPageViewId = async (personObjectId: string) => { const { data } = await findViews({ objectMetadataId: personObjectId, - gqlFields: 'id key name', + gqlFields: 'id key name type', expectToFail: false, }); - const allPeopleView = data?.getViews.find( - (view) => view.key === ViewKey.INDEX, + const recordPageView = data?.getViews.find( + (view) => view.type === ViewType.FIELDS_WIDGET, ); - if (!allPeopleView) { - throw new Error('Standard allPeople (INDEX) view not found for Person'); + if (!recordPageView) { + throw new Error('Standard record-page view not found for Person'); } - return allPeopleView.id; + return recordPageView.id; }; -const findAllPeopleViewFields = async (viewId: string) => { +const findRecordPageViewFields = async (viewId: string) => { const { data } = await findViewFields({ viewId, gqlFields: VIEW_FIELD_GQL_FIELDS, @@ -106,10 +107,10 @@ describe('Manifest update - standalone view fields on existing views', () => { }); }); - it('attaches a standalone view field to the standard allPeople view without recreating the view', async () => { + it('attaches a standalone view field to the standard record-page view without recreating the view', async () => { const person = await findPersonObject(); - const allPeopleViewId = await findAllPeopleViewId(person.id); - const standardViewFields = await findAllPeopleViewFields(allPeopleViewId); + const recordPageViewId = await findRecordPageViewId(person.id); + const standardViewFields = await findRecordPageViewFields(recordPageViewId); const standardViewFieldIds = standardViewFields.map( (viewField) => viewField.id, ); @@ -122,7 +123,10 @@ describe('Manifest update - standalone view fields on existing views', () => { viewFields: [ { universalIdentifier: TEST_VIEW_FIELD_ID, - viewUniversalIdentifier: ALL_PEOPLE_VIEW_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: + PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER, + viewFieldGroupUniversalIdentifier: + PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: TEST_FIELD_ID, position: 10, isVisible: true, @@ -143,7 +147,8 @@ describe('Manifest update - standalone view fields on existing views', () => { expect(customField).toBeDefined(); - const viewFieldsAfterSync = await findAllPeopleViewFields(allPeopleViewId); + const viewFieldsAfterSync = + await findRecordPageViewFields(recordPageViewId); const contributedViewField = viewFieldsAfterSync.find( (viewField) => viewField.fieldMetadataId === customField?.id, @@ -151,7 +156,7 @@ describe('Manifest update - standalone view fields on existing views', () => { expect(contributedViewField).toBeDefined(); expect(contributedViewField).toMatchObject({ - viewId: allPeopleViewId, + viewId: recordPageViewId, position: 10, isVisible: true, size: 150, @@ -169,8 +174,8 @@ describe('Manifest update - standalone view fields on existing views', () => { it('removes the contributed view field on uninstall while keeping the standard view intact', async () => { const person = await findPersonObject(); - const allPeopleViewId = await findAllPeopleViewId(person.id); - const standardViewFields = await findAllPeopleViewFields(allPeopleViewId); + const recordPageViewId = await findRecordPageViewId(person.id); + const standardViewFields = await findRecordPageViewFields(recordPageViewId); const standardViewFieldIds = standardViewFields.map( (viewField) => viewField.id, ); @@ -181,7 +186,10 @@ describe('Manifest update - standalone view fields on existing views', () => { viewFields: [ { universalIdentifier: TEST_VIEW_FIELD_ID, - viewUniversalIdentifier: ALL_PEOPLE_VIEW_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier: + PERSON_RECORD_PAGE_VIEW_UNIVERSAL_IDENTIFIER, + viewFieldGroupUniversalIdentifier: + PERSON_RECORD_PAGE_GENERAL_GROUP_UNIVERSAL_IDENTIFIER, fieldMetadataUniversalIdentifier: TEST_FIELD_ID, position: 10, isVisible: true, @@ -199,7 +207,7 @@ describe('Manifest update - standalone view fields on existing views', () => { expect(customFieldId).toBeDefined(); expect( - (await findAllPeopleViewFields(allPeopleViewId)).some( + (await findRecordPageViewFields(recordPageViewId)).some( (viewField) => viewField.fieldMetadataId === customFieldId, ), ).toBe(true); @@ -209,12 +217,14 @@ describe('Manifest update - standalone view fields on existing views', () => { expectToFail: false, }); - const allPeopleViewIdAfterUninstall = await findAllPeopleViewId(person.id); + const recordPageViewIdAfterUninstall = await findRecordPageViewId( + person.id, + ); - expect(allPeopleViewIdAfterUninstall).toBe(allPeopleViewId); + expect(recordPageViewIdAfterUninstall).toBe(recordPageViewId); const viewFieldsAfterUninstall = - await findAllPeopleViewFields(allPeopleViewId); + await findRecordPageViewFields(recordPageViewId); expect( viewFieldsAfterUninstall.some( @@ -230,55 +240,4 @@ describe('Manifest update - standalone view fields on existing views', () => { ).toBe(true); } }, 60000); - - it('rejects two standalone view fields targeting the same field on the same view', async () => { - const { errors } = await syncApplication({ - manifest: buildManifest({ - fields: [personFieldManifest], - viewFields: [ - { - universalIdentifier: TEST_VIEW_FIELD_ID, - viewUniversalIdentifier: ALL_PEOPLE_VIEW_UNIVERSAL_IDENTIFIER, - fieldMetadataUniversalIdentifier: TEST_FIELD_ID, - position: 10, - isVisible: true, - }, - { - universalIdentifier: TEST_SECOND_VIEW_FIELD_ID, - viewUniversalIdentifier: ALL_PEOPLE_VIEW_UNIVERSAL_IDENTIFIER, - fieldMetadataUniversalIdentifier: TEST_FIELD_ID, - position: 11, - isVisible: true, - }, - ], - }), - expectToFail: true, - }); - - expect(errors).toBeDefined(); - expect(errors.length).toBeGreaterThan(0); - expect(errors[0].extensions.code).toBe('METADATA_VALIDATION_FAILED'); - }, 60000); - - it('rejects a standalone view field whose target view does not exist', async () => { - const { errors } = await syncApplication({ - manifest: buildManifest({ - fields: [personFieldManifest], - viewFields: [ - { - universalIdentifier: TEST_VIEW_FIELD_ID, - viewUniversalIdentifier: uuidv4(), - fieldMetadataUniversalIdentifier: TEST_FIELD_ID, - position: 10, - isVisible: true, - }, - ], - }), - expectToFail: true, - }); - - expect(errors).toBeDefined(); - expect(errors.length).toBeGreaterThan(0); - expect(errors[0].extensions.code).toBe('METADATA_VALIDATION_FAILED'); - }, 60000); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts index 0bd3fa62aa..02f75bdf71 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/successful-sync-application-workspace-migration.integration-spec.ts @@ -20,7 +20,7 @@ import { import { FieldMetadataType } from 'twenty-shared/types'; import { v4 as uuidv4 } from 'uuid'; -const TEST_APP_ID = uuidv4(); +const TEST_APP_ID = '0c17e907-f32a-4526-98e4-9addd4302b1a'; const TEST_ROLE_ID = uuidv4(); const TEST_FIELD_ID = uuidv4(); const TEST_SKILL_ID = uuidv4(); @@ -317,11 +317,12 @@ describe('syncApplication', () => { }, 60000); it('should delete old field and create equivalent one when field universalIdentifier changes', async () => { - const originalFieldId = uuidv4(); - const updatedFieldId = uuidv4(); + const originalFieldId = '8abbef24-f8c3-41d5-826b-73a02825e712'; + const updatedFieldId = 'a4262080-673d-430a-883b-c5d04610abf1'; const testObject = buildDefaultObjectManifest({ applicationUniversalIdentifier: TEST_APP_ID, + universalIdentifier: '8fc4bfec-01d8-4404-bd1a-dc90fe8e7699', nameSingular: 'ticket', namePlural: 'tickets', labelSingular: 'Ticket', diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap index 4ddeb1519d..0f2612960e 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`updateOne FieldMetadataService morph relation fields v2 - Add one target Should fail adding one object metadata target to pre-existing morph relation where object is already a target 1`] = ` { @@ -71,12 +71,49 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe "type": "create", }, ], + "viewField": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + ], }, - "message": "Validation failed for 2 fieldMetadata, 1 index", + "message": "Validation failed for 2 fieldMetadata, 2 viewFields, 1 index", "summary": { "fieldMetadata": 2, "index": 1, - "totalErrors": 3, + "totalErrors": 5, + "viewField": 2, }, "userFriendlyMessage": "Many validation errors", }, @@ -144,12 +181,49 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe "type": "create", }, ], + "viewField": [ + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataUniversalIdentifier": Any, + "universalIdentifier": Any, + "viewUniversalIdentifier": Any, + }, + "metadataName": "viewField", + "status": "fail", + "type": "create", + }, + ], }, - "message": "Validation failed for 2 fieldMetadata, 1 index", + "message": "Validation failed for 2 fieldMetadata, 2 viewFields, 1 index", "summary": { "fieldMetadata": 2, "index": 1, - "totalErrors": 3, + "totalErrors": 5, + "viewField": 2, }, "userFriendlyMessage": "Many validation errors", }, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts index 5fc64ae123..44b3373a62 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts @@ -5,6 +5,7 @@ import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object import { updateOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata.util'; import { findViewFields } from 'test/integration/metadata/suites/view-field/utils/find-view-fields.util'; import { findViews } from 'test/integration/metadata/suites/view/utils/find-views.util'; +import { ViewType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; @@ -33,7 +34,18 @@ describe('View side effect on object creation', () => { createdObjectMetadataId = undefined; }); - it('should create a view and view field for default custom objects standard fields on object metadata creation', async () => { + const objectCreationGqlFields = ` + id + labelPlural + description + labelSingular + namePlural + nameSingular + icon + isLabelSyncedWithName + `; + + const createDishesObject = async () => { const { labelPlural, description, @@ -50,16 +62,6 @@ describe('View side effect on object creation', () => { icon: 'IconBuildingSkyscraper', isLabelSyncedWithName: false, }; - const objectCreationGqlFields = ` - id - labelPlural - description - labelSingular - namePlural - nameSingular - icon - isLabelSyncedWithName - `; const { data: { createOneObject }, @@ -69,6 +71,20 @@ describe('View side effect on object creation', () => { gqlFields: objectCreationGqlFields, }); + return createOneObject; + }; + + const findIndexView = async (objectMetadataId: string) => { + const { + data: { getViews }, + } = await findViews({ objectMetadataId, expectToFail: false }); + + return getViews.find((view) => view.key === 'INDEX'); + }; + + it('should engine-provision the INDEX view and its view fields on object metadata creation', async () => { + const createOneObject = await createDishesObject(); + createdObjectMetadataId = createOneObject.id; const { @@ -80,19 +96,93 @@ describe('View side effect on object creation', () => { expect(createdViews).toBeDefined(); expect(createdViews.length).toBe(2); - const [firstView] = createdViews; - expect(firstView).toMatchObject>({ + const indexView = createdViews.find((view) => view.key === 'INDEX'); + + if (!isDefined(indexView)) { + throw new Error('expected an INDEX view to be provisioned'); + } + + expect(indexView).toMatchObject>({ objectMetadataId: createdObjectMetadataId, + type: ViewType.TABLE, }); const { - data: { getViewFields: createdViewFields }, + data: { getViewFields: indexViewFields }, } = await findViewFields({ - viewId: firstView.id, + viewId: indexView.id, expectToFail: false, }); - expect(createdViewFields.length).toBe(5); + expect(indexViewFields.length).toBe(5); + }); + + it('should keep the same INDEX view when the object is renamed (lossless, deterministic identifier)', async () => { + const createOneObject = await createDishesObject(); + + createdObjectMetadataId = createOneObject.id; + + const indexViewBeforeRename = await findIndexView(createdObjectMetadataId); + + if (!isDefined(indexViewBeforeRename)) { + throw new Error('expected an INDEX view to be provisioned'); + } + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: createdObjectMetadataId, + updatePayload: { + labelPlural: 'Renamed dishes', + labelSingular: 'Renamed dish', + }, + }, + }); + + const indexViewAfterRename = await findIndexView(createdObjectMetadataId); + + if (!isDefined(indexViewAfterRename)) { + throw new Error('expected the INDEX view to survive the rename'); + } + + expect(indexViewAfterRename.id).toBe(indexViewBeforeRename.id); + + const { + data: { getViewFields: indexViewFieldsAfterRename }, + } = await findViewFields({ + viewId: indexViewAfterRename.id, + expectToFail: false, + }); + + expect(indexViewFieldsAfterRename.length).toBe(5); + }); + + it('should cascade-delete the INDEX view and its view fields when the object is deleted', async () => { + const createOneObject = await createDishesObject(); + const objectMetadataId = createOneObject.id; + + const indexView = await findIndexView(objectMetadataId); + + expect(isDefined(indexView)).toBe(true); + + await updateOneObjectMetadata({ + expectToFail: false, + input: { + idToUpdate: objectMetadataId, + updatePayload: { isActive: false }, + }, + }); + + await deleteOneObjectMetadata({ + input: { idToDelete: objectMetadataId }, + expectToFail: false, + }); + + const { + data: { getViews: viewsAfterDelete }, + } = await findViews({ objectMetadataId, expectToFail: false }); + + expect(viewsAfterDelete.length).toBe(0); }); }); diff --git a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts index 639a2a28aa..afee466c09 100644 --- a/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/view.integration-spec.ts @@ -13,8 +13,6 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name' import { assertViewStructure } from 'test/integration/utils/view-test.util'; import { ViewOpenRecordIn, ViewType } from 'twenty-shared/types'; -import { ViewKey } from 'twenty-shared/types'; - describe('View REST API', () => { let testObjectMetadataId: string; let testSelectFieldMetadataId: string; @@ -107,7 +105,7 @@ describe('View REST API', () => { name: viewName, icon: 'IconTable', type: ViewType.TABLE, - key: ViewKey.INDEX, + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, @@ -121,7 +119,7 @@ describe('View REST API', () => { objectMetadataId: testObjectMetadataId, icon: 'IconTable', type: ViewType.TABLE, - key: ViewKey.INDEX, + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, @@ -161,7 +159,7 @@ describe('View REST API', () => { name: viewName, icon: 'IconTable', type: ViewType.TABLE, - key: ViewKey.INDEX, + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, @@ -202,7 +200,7 @@ describe('View REST API', () => { name: viewName, icon: 'IconTable', type: ViewType.TABLE, - key: ViewKey.INDEX, + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, @@ -262,7 +260,7 @@ describe('View REST API', () => { name: viewName, icon: 'IconTable', type: ViewType.TABLE, - key: ViewKey.INDEX, + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, diff --git a/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts b/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts index 481597fffc..c3b07f4f94 100644 --- a/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts +++ b/packages/twenty-server/test/integration/rest/utils/view-rest-api.util.ts @@ -56,7 +56,7 @@ export const createTestViewWithRestApi = async ( objectMetadataId, icon: 'IconTable', type: ViewType.TABLE, - key: 'INDEX', + key: null, position: 0, isCompact: false, openRecordIn: ViewOpenRecordIn.SIDE_PANEL, diff --git a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-fields-widget-view-universal-identifier.util.spec.ts b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-fields-widget-view-universal-identifier.util.spec.ts new file mode 100644 index 0000000000..69cdfbb67a --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-fields-widget-view-universal-identifier.util.spec.ts @@ -0,0 +1,15 @@ +import { getFieldsWidgetViewUniversalIdentifier } from '@/application/deterministic-identifier/get-fields-widget-view-universal-identifier.util'; + +const APP = '11111111-1111-4111-8111-111111111111'; +const WIDGET = '88888888-8888-4888-8888-888888888888'; + +describe('getFieldsWidgetViewUniversalIdentifier', () => { + it('derives a deterministic id from its 1:1 FIELDS page-layout widget', () => { + expect( + getFieldsWidgetViewUniversalIdentifier({ + applicationUniversalIdentifier: APP, + pageLayoutWidgetUniversalIdentifier: WIDGET, + }), + ).toBe('5f45a1bd-97df-56f7-a8ad-7839ef0045b5'); + }); +}); 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 new file mode 100644 index 0000000000..a740c2c2f4 --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-system-view-universal-identifier.util.spec.ts @@ -0,0 +1,17 @@ +import { getSystemViewUniversalIdentifier } from '@/application/deterministic-identifier/get-system-view-universal-identifier.util'; +import { ViewKey } from '@/types/ViewKey'; + +const APP = '11111111-1111-4111-8111-111111111111'; +const OBJECT = '22222222-2222-4222-8222-222222222222'; + +describe('getSystemViewUniversalIdentifier', () => { + it('derives a deterministic id from the stable INDEX view key within its object', () => { + expect( + getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: APP, + objectUniversalIdentifier: OBJECT, + viewKey: ViewKey.INDEX, + }), + ).toBe('3803a536-0158-554c-bfb4-e5492323e57f'); + }); +}); diff --git a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-view-universal-identifier.util.spec.ts b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-view-universal-identifier.util.spec.ts index feadc5d12a..d0edc25ade 100644 --- a/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-view-universal-identifier.util.spec.ts +++ b/packages/twenty-shared/src/application/deterministic-identifier/__tests__/get-view-universal-identifier.util.spec.ts @@ -1,12 +1,7 @@ -import { - getFieldsWidgetViewUniversalIdentifier, - getIndexViewUniversalIdentifier, - getViewUniversalIdentifier, -} from '@/application/deterministic-identifier/get-view-universal-identifier.util'; +import { getViewUniversalIdentifier } from '@/application/deterministic-identifier/get-view-universal-identifier.util'; const APP = '11111111-1111-4111-8111-111111111111'; const OBJECT = '22222222-2222-4222-8222-222222222222'; -const WIDGET = '88888888-8888-4888-8888-888888888888'; describe('getViewUniversalIdentifier', () => { it('derives a deterministic id from the view name within its object', () => { @@ -19,25 +14,3 @@ describe('getViewUniversalIdentifier', () => { ).toBe('125140c3-eca9-560d-9020-dad4ac1ebab6'); }); }); - -describe('getIndexViewUniversalIdentifier', () => { - it('derives a deterministic id from the stable INDEX view key within its object', () => { - expect( - getIndexViewUniversalIdentifier({ - applicationUniversalIdentifier: APP, - objectUniversalIdentifier: OBJECT, - }), - ).toBe('3803a536-0158-554c-bfb4-e5492323e57f'); - }); -}); - -describe('getFieldsWidgetViewUniversalIdentifier', () => { - it('derives a deterministic id from its 1:1 FIELDS page-layout widget', () => { - expect( - getFieldsWidgetViewUniversalIdentifier({ - applicationUniversalIdentifier: APP, - pageLayoutWidgetUniversalIdentifier: WIDGET, - }), - ).toBe('5f45a1bd-97df-56f7-a8ad-7839ef0045b5'); - }); -}); diff --git a/packages/twenty-shared/src/application/deterministic-identifier/get-fields-widget-view-universal-identifier.util.ts b/packages/twenty-shared/src/application/deterministic-identifier/get-fields-widget-view-universal-identifier.util.ts new file mode 100644 index 0000000000..c896b4b484 --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/get-fields-widget-view-universal-identifier.util.ts @@ -0,0 +1,14 @@ +import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util'; + +export const getFieldsWidgetViewUniversalIdentifier = ({ + applicationUniversalIdentifier, + pageLayoutWidgetUniversalIdentifier, +}: { + applicationUniversalIdentifier: string; + pageLayoutWidgetUniversalIdentifier: string; +}): string => + computeDeterministicUuid({ + entityNamespace: 'view', + value: pageLayoutWidgetUniversalIdentifier, + applicationUniversalIdentifier, + }); 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 new file mode 100644 index 0000000000..51b88da666 --- /dev/null +++ b/packages/twenty-shared/src/application/deterministic-identifier/get-system-view-universal-identifier.util.ts @@ -0,0 +1,17 @@ +import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util'; +import { type ViewKey } from '@/types/ViewKey'; + +export const getSystemViewUniversalIdentifier = ({ + applicationUniversalIdentifier, + objectUniversalIdentifier, + viewKey, +}: { + applicationUniversalIdentifier: string; + objectUniversalIdentifier: string; + viewKey: ViewKey; +}): string => + computeDeterministicUuid({ + entityNamespace: 'view', + value: `${objectUniversalIdentifier}:${viewKey}`, + applicationUniversalIdentifier, + }); diff --git a/packages/twenty-shared/src/application/deterministic-identifier/get-view-universal-identifier.util.ts b/packages/twenty-shared/src/application/deterministic-identifier/get-view-universal-identifier.util.ts index c0f003af22..e8c6ea8a44 100644 --- a/packages/twenty-shared/src/application/deterministic-identifier/get-view-universal-identifier.util.ts +++ b/packages/twenty-shared/src/application/deterministic-identifier/get-view-universal-identifier.util.ts @@ -1,7 +1,5 @@ import { computeDeterministicUuid } from '@/application/deterministic-identifier/compute-deterministic-uuid.util'; -import { ViewKey } from '@/types/ViewKey'; -// A view is identified by its name within its object. export const getViewUniversalIdentifier = ({ applicationUniversalIdentifier, objectUniversalIdentifier, @@ -16,33 +14,3 @@ export const getViewUniversalIdentifier = ({ value: `${objectUniversalIdentifier}:${name}`, applicationUniversalIdentifier, }); - -// The INDEX view is an object's singleton main table view; its name is -// server-generated, so it is keyed by the stable INDEX view key. -export const getIndexViewUniversalIdentifier = ({ - applicationUniversalIdentifier, - objectUniversalIdentifier, -}: { - applicationUniversalIdentifier: string; - objectUniversalIdentifier: string; -}): string => - computeDeterministicUuid({ - entityNamespace: 'view', - value: `${objectUniversalIdentifier}:${ViewKey.INDEX}`, - applicationUniversalIdentifier, - }); - -// A fields-widget view is 1:1 with its FIELDS page-layout widget, so it is keyed -// by that widget's deterministic universalIdentifier (its name is server-generated). -export const getFieldsWidgetViewUniversalIdentifier = ({ - applicationUniversalIdentifier, - pageLayoutWidgetUniversalIdentifier, -}: { - applicationUniversalIdentifier: string; - pageLayoutWidgetUniversalIdentifier: string; -}): string => - computeDeterministicUuid({ - entityNamespace: 'view', - value: pageLayoutWidgetUniversalIdentifier, - applicationUniversalIdentifier, - }); diff --git a/packages/twenty-shared/src/application/index.ts b/packages/twenty-shared/src/application/index.ts index e637ec716e..245432fc62 100644 --- a/packages/twenty-shared/src/application/index.ts +++ b/packages/twenty-shared/src/application/index.ts @@ -54,6 +54,7 @@ export { export { getConnectionProviderUniversalIdentifier } from './deterministic-identifier/get-connection-provider-universal-identifier.util'; export { getFieldPermissionUniversalIdentifier } from './deterministic-identifier/get-field-permission-universal-identifier.util'; export { getFieldUniversalIdentifier } from './deterministic-identifier/get-field-universal-identifier.util'; +export { getFieldsWidgetViewUniversalIdentifier } from './deterministic-identifier/get-fields-widget-view-universal-identifier.util'; export { getFrontComponentUniversalIdentifier } from './deterministic-identifier/get-front-component-universal-identifier.util'; export { getIndexUniversalIdentifier } from './deterministic-identifier/get-index-universal-identifier.util'; export { getLogicFunctionUniversalIdentifier } from './deterministic-identifier/get-logic-function-universal-identifier.util'; @@ -79,16 +80,13 @@ 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 { 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'; export { getViewFilterUniversalIdentifier } from './deterministic-identifier/get-view-filter-universal-identifier.util'; export { getViewGroupUniversalIdentifier } from './deterministic-identifier/get-view-group-universal-identifier.util'; export { getViewSortUniversalIdentifier } from './deterministic-identifier/get-view-sort-universal-identifier.util'; -export { - getViewUniversalIdentifier, - getIndexViewUniversalIdentifier, - getFieldsWidgetViewUniversalIdentifier, -} from './deterministic-identifier/get-view-universal-identifier.util'; +export { getViewUniversalIdentifier } from './deterministic-identifier/get-view-universal-identifier.util'; export { SyncableEntity } from './enums/syncable-entities.enum'; export type { RegularFieldManifest, diff --git a/packages/twenty-shared/src/application/viewManifestType.ts b/packages/twenty-shared/src/application/viewManifestType.ts index e6b59d72b0..061f7c3e4e 100644 --- a/packages/twenty-shared/src/application/viewManifestType.ts +++ b/packages/twenty-shared/src/application/viewManifestType.ts @@ -68,6 +68,11 @@ export type ViewManifest = SyncableEntityOptions & { name: string; objectUniversalIdentifier: string; type?: ViewType; + /** + * @deprecated The INDEX key is reserved for the engine-owned default view, + * which the server provisions automatically for every object. This field is + * ignored: manifest views are always created as additional views. + */ key?: ViewKey; icon?: string; position?: number; diff --git a/packages/twenty-shared/src/metadata/__tests__/__snapshots__/standardObjectUniversalIdentifiers.test.ts.snap b/packages/twenty-shared/src/metadata/__tests__/__snapshots__/standardObjectUniversalIdentifiers.test.ts.snap index 585b2dec59..03165614ef 100644 --- a/packages/twenty-shared/src/metadata/__tests__/__snapshots__/standardObjectUniversalIdentifiers.test.ts.snap +++ b/packages/twenty-shared/src/metadata/__tests__/__snapshots__/standardObjectUniversalIdentifiers.test.ts.snap @@ -93,40 +93,40 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-bd3d-4c60-8dca-571c71d4447a", "views": { "allAttachments": { - "universalIdentifier": "3f7f3363-7087-44cc-902d-5e8904262316", + "universalIdentifier": "e0c5bfc8-2aa8-5407-9d7e-f374055914b2", "viewFields": { "createdAt": { - "universalIdentifier": "6c092c26-b1cb-488f-ae2e-5af4bec1162b", + "universalIdentifier": "cd0ab629-435f-5d59-9b14-1d2ee00954d8", }, "createdBy": { - "universalIdentifier": "fa363372-0fdf-4bb3-bdf1-0ead354b9225", + "universalIdentifier": "9c0456cf-ab42-596b-b381-048cf5718117", }, "file": { - "universalIdentifier": "873cf114-5477-4b62-9023-7ea6ad69fbe5", + "universalIdentifier": "2c641732-02a0-5aeb-9262-ff60973b7cf2", }, "name": { - "universalIdentifier": "be56712f-d7a6-4fbe-b92b-d750f0708a0a", + "universalIdentifier": "d5a4f58e-abf1-52ce-a315-ea7b18e4a946", }, "targetCompany": { - "universalIdentifier": "b335ad04-059e-4c36-8666-f40431849044", + "universalIdentifier": "7f76f6c5-078d-5064-89b4-e0042ee4e169", }, "targetDashboard": { - "universalIdentifier": "bcc6d6e1-7c0b-4291-9270-66e42024d8dd", + "universalIdentifier": "4f1ae4ae-c1c9-5b2c-8f6f-9435171fca48", }, "targetNote": { - "universalIdentifier": "fc8dba49-bcf2-41b8-a435-0c4a3bbf2af6", + "universalIdentifier": "6e24a74a-6d3d-54ce-afd0-7c55daf79d81", }, "targetOpportunity": { - "universalIdentifier": "15f2d457-dc09-4c52-bf2a-47083d6bf017", + "universalIdentifier": "4e0a4cf7-8e45-5688-90a6-da6d67544f72", }, "targetPerson": { - "universalIdentifier": "73a4c3a7-c7f9-4ed6-a2b6-117d7efad0f3", + "universalIdentifier": "8cff29b2-23e8-5190-93da-f1b22faea495", }, "targetTask": { - "universalIdentifier": "c2913c5e-6cc6-438d-9c2f-3212a9b2a82b", + "universalIdentifier": "cc9c44ea-c27e-5f57-9094-85cf14dae3e2", }, "targetWorkflow": { - "universalIdentifier": "11fcf58b-dbab-42dd-be67-689462111070", + "universalIdentifier": "75d1d092-412f-57b1-9e74-e3f8285a3f0c", }, }, }, @@ -173,16 +173,16 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-0408-4f38-b8a8-4d5e3e26e24d", "views": { "allBlocklists": { - "universalIdentifier": "5a98e88c-67c2-4f61-a5ab-a0d3d6a836bb", + "universalIdentifier": "2ee7be0b-4112-5543-9f29-04c933fd4f98", "viewFields": { "createdAt": { - "universalIdentifier": "e7cfcf05-2676-4d43-9eee-4da1016b12ff", + "universalIdentifier": "fc86c1d0-a6f1-58c4-b32c-ddbffdeda2da", }, "handle": { - "universalIdentifier": "155ae00d-0def-4f62-9473-8a8efa209eee", + "universalIdentifier": "07282d15-ed89-5504-9904-cd242125e9f2", }, "workspaceMember": { - "universalIdentifier": "05a2f0b9-f2ef-4729-bc42-9e2ad2a34fb2", + "universalIdentifier": "20253618-79e4-57e8-b18f-f506657d5132", }, }, }, @@ -260,19 +260,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-491b-4aaa-9825-afd1bae6ae00", "views": { "allCalendarChannelEventAssociations": { - "universalIdentifier": "001893be-c06c-4ba1-9f18-53bd26f0179f", + "universalIdentifier": "66887c1c-c33a-52b6-8dd1-0714e504ffec", "viewFields": { "calendarChannelId": { - "universalIdentifier": "e3adffd2-d820-4c89-912c-34908d90057e", + "universalIdentifier": "6504f7e7-3a66-54b2-adc1-53d90fc7d9e3", }, "calendarEvent": { - "universalIdentifier": "35656a84-ecb8-4075-a610-8b538d6f8120", + "universalIdentifier": "9be9bb56-f65d-53ee-a6e6-4d01a6e9cb58", }, "createdAt": { - "universalIdentifier": "8ca74f2f-210b-4afc-81f0-506047400e82", + "universalIdentifier": "2ee9fd11-c7a6-54c8-af41-572ed8cecbd7", }, "eventExternalId": { - "universalIdentifier": "f779d7e8-f1d8-44a7-b0ef-4409c9b6b466", + "universalIdentifier": "55ea0442-7664-5da2-bc21-d02e101acdcc", }, }, }, @@ -382,31 +382,31 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-8f1d-4eef-9f85-0d1965e27221", "views": { "allCalendarEvents": { - "universalIdentifier": "20202020-c001-4c01-8c01-ca1ebe0ca001", + "universalIdentifier": "d581f9a1-1546-5eef-b5a4-4c7a088dc6fa", "viewFields": { "conferenceLink": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf06", + "universalIdentifier": "caee37ae-989a-5d95-ad26-e42530d5a65c", }, "createdAt": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf08", + "universalIdentifier": "0a502413-f875-5c1d-aa60-93e6f412568e", }, "endsAt": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf03", + "universalIdentifier": "65fbb3d7-e21c-52f6-a90c-38a424d05284", }, "isCanceled": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf07", + "universalIdentifier": "ef47c862-92b5-5d72-8e3c-7806244b0a70", }, "isFullDay": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf04", + "universalIdentifier": "b3f5e6c6-80bf-5ef3-b8d7-1bc521ef8054", }, "location": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf05", + "universalIdentifier": "ac181cfc-c9cb-50f5-89eb-19c01615b0dc", }, "startsAt": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf02", + "universalIdentifier": "38902ac0-92dd-5889-bcc2-0e3956a7f18d", }, "title": { - "universalIdentifier": "20202020-cf01-4c01-8c01-ca1ebe0caf01", + "universalIdentifier": "292e2bd9-118e-56a2-bafc-e182f4f323a7", }, }, }, @@ -523,31 +523,31 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-a1c3-47a6-9732-27e5b1e8436d", "views": { "allCalendarEventParticipants": { - "universalIdentifier": "5228d634-6b69-4a43-be5c-e778fa6fe779", + "universalIdentifier": "23b1277b-8859-54ea-9106-9e6d43e5adc4", "viewFields": { "calendarEvent": { - "universalIdentifier": "d9c2f346-b83b-48ae-98d0-e344f97248cd", + "universalIdentifier": "5ef7cac8-d293-559f-88a7-5a4d0234c4c5", }, "createdAt": { - "universalIdentifier": "63d9d40d-e40c-410c-a14c-2f36c64c3e69", + "universalIdentifier": "41f8cf98-cd93-500d-9b9c-1e1af2e6c3cb", }, "displayName": { - "universalIdentifier": "3cadc470-9231-4027-9bbe-60e934edb483", + "universalIdentifier": "f96e757e-aa84-5c05-8922-9f962eb3854f", }, "handle": { - "universalIdentifier": "4140bd68-55e8-475c-8724-7f9f97634a9f", + "universalIdentifier": "e3c1165d-aa09-5636-ac37-a391bacc602e", }, "isOrganizer": { - "universalIdentifier": "684972f9-c5fe-4fff-bdec-2fc5511c938c", + "universalIdentifier": "949e222c-dbe7-5696-b386-ed3a82d8b452", }, "person": { - "universalIdentifier": "86546244-9e3d-40e4-87cd-cbc82a353d2e", + "universalIdentifier": "e236d415-822a-5847-a90c-f0699eeb5cc7", }, "responseStatus": { - "universalIdentifier": "dd0ab0bd-7f33-48fa-9461-fb5d085a2f9f", + "universalIdentifier": "0f3e3c0c-d8f9-57d9-99d5-66407347f210", }, "workspaceMember": { - "universalIdentifier": "542141b0-ac85-4c43-867b-8d7f559b07ae", + "universalIdentifier": "e54eca2e-7fd9-5743-a514-348067775949", }, }, }, @@ -667,19 +667,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "ce19efb9-710f-45b2-b141-473abbeea60b", "views": { "allCallRecordings": { - "universalIdentifier": "c395b55e-88f0-4d5b-a1fb-0d38b50e0b19", + "universalIdentifier": "6676d746-71e4-5dff-9bf1-418b92c26253", "viewFields": { "recordingRequestStatus": { - "universalIdentifier": "3bdedacd-0fd5-4175-8d28-2fe41bb5aa77", + "universalIdentifier": "883d1aca-4113-50ab-b83e-e2e2292bb5aa", }, "startedAt": { - "universalIdentifier": "3b96351f-66ed-4fa6-acb6-698647573af7", + "universalIdentifier": "23d91b8e-e413-5e10-b818-17c3359fe068", }, "status": { - "universalIdentifier": "6c4a81a2-d9c1-4f82-984c-f97e083ca710", + "universalIdentifier": "c7e2829f-4eba-5d7f-a1c7-a18ed8bd8025", }, "title": { - "universalIdentifier": "b1d5051b-071d-4514-93cf-704724cdc8f6", + "universalIdentifier": "fcf0cedc-6d25-53ed-a587-cfb4b0958071", }, }, }, @@ -799,28 +799,28 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-b374-4779-a561-80086cb2e17f", "views": { "allCompanies": { - "universalIdentifier": "20202020-a001-4a01-8a01-c0aba11c0001", + "universalIdentifier": "cb8dc074-a42f-520b-85e5-ab85a1b40b20", "viewFields": { "accountOwner": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf004", + "universalIdentifier": "90b29c8d-d6a3-57be-8f3b-879f2807a0a7", }, "address": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf008", + "universalIdentifier": "68cd0e8b-f730-54f5-90a0-45798bb5bb70", }, "createdAt": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf005", + "universalIdentifier": "b8b2c693-4f3e-5471-afe2-1bd70dcd9b16", }, "createdBy": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf003", + "universalIdentifier": "ec94701d-acce-58b5-bd81-75d585a68cf7", }, "domainName": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf002", + "universalIdentifier": "4b4afe40-aa94-5bca-92a3-b84ccb846811", }, "linkedinLink": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf007", + "universalIdentifier": "fa5a7f00-9751-5ab1-a410-fe144fedd2dc", }, "name": { - "universalIdentifier": "20202020-af01-4a01-8a01-c0aba11cf001", + "universalIdentifier": "87bbfcc6-3be8-58a5-852c-f02c9c9bc009", }, }, }, @@ -937,19 +937,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-3840-4b6d-9425-0c5188b05ca8", "views": { "allDashboards": { - "universalIdentifier": "20202020-a012-4a12-8a12-da5ab0b0a001", + "universalIdentifier": "0769edd6-ff42-5c4b-80c4-8aa1d07afba6", "viewFields": { "createdAt": { - "universalIdentifier": "20202020-af12-4a12-8a12-da5ab0b0af03", + "universalIdentifier": "f586827d-ed83-51bc-a862-741bd45ead77", }, "createdBy": { - "universalIdentifier": "20202020-af12-4a12-8a12-da5ab0b0af02", + "universalIdentifier": "79f57be6-3262-517b-b592-4e791be5806e", }, "title": { - "universalIdentifier": "20202020-af12-4a12-8a12-da5ab0b0af01", + "universalIdentifier": "e0fd54ca-2197-5ba8-ac87-9b7b39f359d0", }, "updatedAt": { - "universalIdentifier": "20202020-af12-4a12-8a12-da5ab0b0af04", + "universalIdentifier": "d37846b6-01a9-54f9-ab44-43a39375974d", }, }, }, @@ -1023,28 +1023,28 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-3f6b-4425-80ab-e468899ab4b2", "views": { "allMessages": { - "universalIdentifier": "20202020-d001-4d01-8d01-ae55a9e5a001", + "universalIdentifier": "8ac6d466-4071-5ce9-b635-5162034fa3f5", "viewFields": { "createdAt": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af07", + "universalIdentifier": "b214ac38-da59-5c1c-ae7e-189dc11f3f37", }, "headerMessageId": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af05", + "universalIdentifier": "9139b1ad-8555-571c-8433-f038de5a3c82", }, "messageParticipants": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af03", + "universalIdentifier": "3ad5730a-168c-5d32-be43-71c982ce9505", }, "messageThread": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af02", + "universalIdentifier": "5072b4d6-d708-5cb5-a97a-7ae2a12b1337", }, "receivedAt": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af04", + "universalIdentifier": "c8df5f80-0d07-5ecd-ba69-8f975bbdc51a", }, "subject": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af01", + "universalIdentifier": "dfe53214-95ef-5dae-9c94-bf0c008f5c5d", }, "text": { - "universalIdentifier": "20202020-df01-4d01-8d01-ae55a9e5af06", + "universalIdentifier": "1aeb24ca-72db-5735-ab13-f9bd99cab092", }, }, }, @@ -1136,43 +1136,43 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "238acb94-dd4c-4036-bc55-19b99d821efd", "views": { "allMessageCampaigns": { - "universalIdentifier": "ffedb368-33f0-43a7-b84e-e622b4e97be9", + "universalIdentifier": "8094a87e-310b-58e7-9c05-a76c59ac6535", "viewFields": { "bouncedCount": { - "universalIdentifier": "135d3fb9-bc45-4426-980b-9836ed792e3a", + "universalIdentifier": "3c273c7f-b577-5213-8597-198dac6fdae1", }, "complainedCount": { - "universalIdentifier": "9957dc6d-05ae-4829-ae7d-7cdf9239cdd0", + "universalIdentifier": "30317d2b-5212-56be-a5fe-0ab15722dde3", }, "createdAt": { - "universalIdentifier": "6dffa47d-3128-4d54-924a-4f67676116c2", + "universalIdentifier": "88484379-8afd-5f7c-ab3c-7d937bb7ed13", }, "failedCount": { - "universalIdentifier": "ad346c8e-f462-499e-89c8-da01d5460dc6", + "universalIdentifier": "2f44ac38-194f-56a7-bd94-a292db4bc210", }, "fromAddress": { - "universalIdentifier": "553a0ea6-1f55-4143-a2d0-8c1ad0cf3dd1", + "universalIdentifier": "2ebb7a5d-e03d-5c6e-955f-4efb99fffb46", }, "list": { - "universalIdentifier": "c0bf6a48-8695-4520-81e6-7462d35033b5", + "universalIdentifier": "13936b8a-0135-5ac3-81d1-f6a11826451e", }, "name": { - "universalIdentifier": "4d40646c-853d-48e6-8ddb-81e6dedabc99", + "universalIdentifier": "5a224d5d-9ae7-53c8-ad6d-720bb62a087b", }, "recipients": { - "universalIdentifier": "2f99f444-4af3-44fd-b08d-46955c4ac2a2", + "universalIdentifier": "40f278c1-2716-5324-8035-c14ea62a4eb7", }, "sentAt": { - "universalIdentifier": "7f4a9d81-afac-4d27-98de-0a2da65114f7", + "universalIdentifier": "a9389297-3927-5364-8c9d-4bcb8039020e", }, "sentCount": { - "universalIdentifier": "0be62e57-3955-49bd-a9ef-8f82f7a6e9aa", + "universalIdentifier": "e55b655a-038e-5257-ab2c-f2966900d2de", }, "status": { - "universalIdentifier": "ef35df97-b1a5-4f16-8b85-5751e5019f63", + "universalIdentifier": "68714967-d906-5082-af8c-2791705ce682", }, "subject": { - "universalIdentifier": "b017c851-e38d-4a81-ab38-0fb10e9f239e", + "universalIdentifier": "2a4fe259-612f-5d10-a451-a047e605917c", }, }, }, @@ -1268,22 +1268,22 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-ad1e-4127-bccb-d83ae04d2ccb", "views": { "allMessageChannelMessageAssociations": { - "universalIdentifier": "a4f465ac-d5cb-4f24-93ac-7a24bafd398e", + "universalIdentifier": "fda3d57b-b54b-57be-af68-f9857f58577c", "viewFields": { "createdAt": { - "universalIdentifier": "af239abd-2c55-4108-a9d8-b5a67f6ca2e2", + "universalIdentifier": "32690396-1e7d-5b95-ad30-94c0c8914957", }, "direction": { - "universalIdentifier": "ca38195e-985c-4880-85e0-26fa143c1ec7", + "universalIdentifier": "c9603fb3-9cef-5747-8d99-86592bff5f3d", }, "message": { - "universalIdentifier": "f9f2de0d-3db5-402b-a733-53be6a4667c8", + "universalIdentifier": "9346a2f9-eff0-5909-af3e-12e52e6eef3a", }, "messageChannelId": { - "universalIdentifier": "b86e652b-04ce-4089-9f71-e190eaf5b798", + "universalIdentifier": "5c6fdb16-a932-53f7-99d6-29e118a06f75", }, "messageExternalId": { - "universalIdentifier": "7fb9801d-ca3d-4b2d-8d55-c922fcf7fefd", + "universalIdentifier": "ce797f5c-ea26-50fc-8d72-2dac766dc498", }, }, }, @@ -1367,16 +1367,16 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-a1b0-40b0-8ab0-5b6c7d8e9f0a", "views": { "allMessageChannelMessageAssociationMessageFolders": { - "universalIdentifier": "775610fe-f1d1-4959-bdc3-0b437059cfeb", + "universalIdentifier": "982c43ec-d2af-5197-aad9-c61f4365a91b", "viewFields": { "createdAt": { - "universalIdentifier": "9da7637e-25c7-4101-8169-b5f6ff159690", + "universalIdentifier": "63b9774c-fd0e-5c17-a1c4-84ca9845f79c", }, "messageChannelMessageAssociation": { - "universalIdentifier": "1251e67a-e795-4bc2-a468-6cfc838b6a0a", + "universalIdentifier": "7f87dc1b-efea-5286-be19-27b171a21a7e", }, "messageFolderId": { - "universalIdentifier": "aff2203d-6439-43b8-9cb4-55e8d78bba43", + "universalIdentifier": "e608de61-b9fe-5747-a224-ab1c32290997", }, }, }, @@ -1454,19 +1454,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "826561ea-4816-411c-baa0-eec5e6ca8866", "views": { "allMessageLists": { - "universalIdentifier": "c72bae18-75e9-4cb0-baeb-379d3529b98f", + "universalIdentifier": "75e21a3c-6c76-58ec-9f14-b2e9ecc93204", "viewFields": { "campaigns": { - "universalIdentifier": "037030a2-9dad-4bfd-957a-313b362172b4", + "universalIdentifier": "8fd26964-23a7-5913-b431-1b83c03bfdfe", }, "createdAt": { - "universalIdentifier": "29f3e7de-c40b-4597-8568-7318c146e4da", + "universalIdentifier": "69d178f4-f4b8-5bc2-a01b-bfc21f3a753a", }, "members": { - "universalIdentifier": "6d314c4b-215a-4094-963a-ff9dd8221aea", + "universalIdentifier": "8e64ce89-0910-58df-a896-ccbe514f3913", }, "name": { - "universalIdentifier": "c9767580-34e6-420b-923d-b4abd8c13d96", + "universalIdentifier": "9deb8cdc-2734-541f-a6c2-d37e144d4747", }, }, }, @@ -1580,28 +1580,28 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-a433-4456-aa2d-fd9cb26b774a", "views": { "allMessageParticipants": { - "universalIdentifier": "8b7fbe7d-dae0-4285-8bdc-ec078a4de870", + "universalIdentifier": "9ffa51b2-4eef-52a7-9761-10b2d2ca8716", "viewFields": { "createdAt": { - "universalIdentifier": "636ff7b6-86b8-49fc-9442-39f4c24ff424", + "universalIdentifier": "9995f355-7956-5f8c-917f-7a3390fd417d", }, "displayName": { - "universalIdentifier": "8d0c8202-b57f-4450-a090-a7eb26aa2299", + "universalIdentifier": "dd4ef6d0-a1d6-5618-a583-d77222d6a768", }, "handle": { - "universalIdentifier": "abcbb5d9-b8c2-46bb-b3cc-ea035be8f3be", + "universalIdentifier": "1eb7f5a3-7e3b-5f9b-9362-27b99b1ca88f", }, "message": { - "universalIdentifier": "ca491a31-8659-4202-9476-f0f72efc80b5", + "universalIdentifier": "a6ae59bd-a5ef-5210-b0bf-ebbcb0386e93", }, "person": { - "universalIdentifier": "26d0f3f1-43d3-425c-930c-81147451d0f8", + "universalIdentifier": "78872b33-6bba-5cf3-bef0-bfaf528e6877", }, "role": { - "universalIdentifier": "55b74f7e-7c58-4fce-a44b-a8d9671ec541", + "universalIdentifier": "009f68a7-695c-582b-833e-501af34480ff", }, "workspaceMember": { - "universalIdentifier": "df62dcbc-c22d-4d34-9fa5-6f70bae02161", + "universalIdentifier": "ca7bd6d7-939d-59eb-bd2d-73777e28c9a5", }, }, }, @@ -1681,19 +1681,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-849a-4c3e-84f5-a25a7d802271", "views": { "allMessageThreads": { - "universalIdentifier": "20202020-d002-4d02-8d02-ae55a9ba2002", + "universalIdentifier": "0f667621-896c-5b8e-8359-3c7c1f8ee1d8", "viewFields": { "createdAt": { - "universalIdentifier": "20202020-df02-4d02-8d02-ae55a9ba2f02", + "universalIdentifier": "98c4c4b8-509c-57a4-b1ba-f66de2df38a2", }, "messages": { - "universalIdentifier": "20202020-df02-4d02-8d02-ae55a9ba2f01", + "universalIdentifier": "0f61a257-5110-5f67-9315-42da5355a6e9", }, "subject": { - "universalIdentifier": "e5f0d32b-2b6a-47bc-b3bd-f32c96594ec1", + "universalIdentifier": "7074b6e0-abd9-5f2c-bf3b-3c834ef37c1d", }, "updatedAt": { - "universalIdentifier": "af2c6ac9-7083-4609-8172-d518441f5e9e", + "universalIdentifier": "cd336056-36de-525b-af30-c5c6c38b917b", }, }, }, @@ -1749,22 +1749,22 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-0b00-45cd-b6f6-6cd806fc6804", "views": { "allNotes": { - "universalIdentifier": "20202020-a005-4a05-8a05-a0be5a11a000", + "universalIdentifier": "2bfba20b-7cfa-5d3c-b683-cf9abde2359d", "viewFields": { "bodyV2": { - "universalIdentifier": "20202020-af05-4a05-8a05-a0be5a11af02", + "universalIdentifier": "3a923b53-057d-52ee-9b12-43ccbe9b9fc2", }, "createdAt": { - "universalIdentifier": "20202020-af05-4a05-8a05-a0be5a11af04", + "universalIdentifier": "6c04d549-a0fb-50ea-8132-557c61ad6f58", }, "createdBy": { - "universalIdentifier": "20202020-af05-4a05-8a05-a0be5a11af03", + "universalIdentifier": "414f18ff-5ce5-589b-8ff3-fe7cad4b0e75", }, "noteTargets": { - "universalIdentifier": "20202020-af05-4a05-8a05-a0be5a11af01", + "universalIdentifier": "1be85e89-a527-5225-a315-3c3fddfdaf86", }, "title": { - "universalIdentifier": "20202020-af05-4a05-8a05-a0be5a11af00", + "universalIdentifier": "55d3619b-d72e-5f2b-8f79-a4ad5106dd76", }, }, }, @@ -1868,22 +1868,22 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-fff0-4b44-be82-bda313884400", "views": { "allNoteTargets": { - "universalIdentifier": "d124d587-ef78-402b-9341-7673e6cea033", + "universalIdentifier": "3962cec0-e2f6-5d27-ab3c-40a5dd6279d0", "viewFields": { "id": { - "universalIdentifier": "f2d912fe-7c6f-4a9c-b808-b7b5a18d2818", + "universalIdentifier": "e296fb5c-74f5-5924-8506-3b16639564c9", }, "note": { - "universalIdentifier": "9d4ac173-d32b-4a44-9dbd-8a47ab844f98", + "universalIdentifier": "de9a35c2-dd69-5b62-94ca-006fc7c4c28f", }, "targetCompany": { - "universalIdentifier": "a9c7f370-4b22-4f29-8e3f-678e91a59576", + "universalIdentifier": "e8175481-58d2-5c1d-a6b4-118448c78073", }, "targetOpportunity": { - "universalIdentifier": "3efeb162-cd03-458b-9c7b-47032d045204", + "universalIdentifier": "eb132124-1725-5528-affc-e4996415e01f", }, "targetPerson": { - "universalIdentifier": "b6f67de5-c5cf-4235-b740-a6a007c8eae3", + "universalIdentifier": "48f31dbf-75fc-5780-a58a-960d9ce5c18b", }, }, }, @@ -1966,25 +1966,25 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-9549-49dd-b2b2-883999db8938", "views": { "allOpportunities": { - "universalIdentifier": "20202020-a003-4a03-8a03-0aa0b1ca1ba0", + "universalIdentifier": "d024c8c9-3021-53d0-b612-9da81b02736f", "viewFields": { "amount": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1bb0", + "universalIdentifier": "22e16aff-d1b4-59ce-902b-fee1f4adb79c", }, "closeDate": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1bb2", + "universalIdentifier": "20b2d9f9-8ad9-59db-8a62-2576692c9ad9", }, "company": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1bb3", + "universalIdentifier": "d0ac287f-801b-54c5-ba49-c842a5e7140c", }, "createdBy": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1bb1", + "universalIdentifier": "fe527f0f-1690-5077-9b9e-cebaa7c4c5d1", }, "name": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1baf", + "universalIdentifier": "4a52d5c5-0243-5c57-ab5e-6d1395beb2cf", }, "pointOfContact": { - "universalIdentifier": "20202020-af03-4a03-8a03-0aa0b1ca1bb4", + "universalIdentifier": "51fbe12a-46c1-50d2-8163-5a50542c600e", }, }, }, @@ -2177,31 +2177,31 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-e674-48e5-a542-72570eee7213", "views": { "allPeople": { - "universalIdentifier": "20202020-a002-4a02-8a02-ae0a1ea11a00", + "universalIdentifier": "0cda0ad4-74da-5ccd-975c-a0e4db43a05a", "viewFields": { "company": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af3", + "universalIdentifier": "3530ea58-3f8f-5e5f-a17a-f68d4ad867ba", }, "createdAt": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af5", + "universalIdentifier": "114a1b7f-71c5-5277-a4e7-b2379d900a99", }, "createdBy": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af2", + "universalIdentifier": "de1a471e-8ae4-533f-870a-c462561b02d2", }, "emails": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af1", + "universalIdentifier": "5731e6bb-b5dc-5793-aaa5-386b0da99fd5", }, "jobTitle": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af7", + "universalIdentifier": "a036c9bc-4e8e-5384-8598-48ed4e516fc6", }, "linkedinLink": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af8", + "universalIdentifier": "42014c11-5951-59eb-b525-bf14d5570eab", }, "name": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af0", + "universalIdentifier": "899d55b0-dd24-534e-af40-65d08b9073ce", }, "phones": { - "universalIdentifier": "20202020-af02-4a02-8a02-ae0a1ea11af4", + "universalIdentifier": "565f0a13-140e-5c59-86b1-7d0ebd18e9dd", }, }, }, @@ -2342,31 +2342,31 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-1ba1-48ba-bc83-ef7e5990ed10", "views": { "allTasks": { - "universalIdentifier": "20202020-a006-4a06-8a06-ba5ca11a1ea0", + "universalIdentifier": "ca2db697-077a-5f45-81e1-513aea4752a1", "viewFields": { "assignee": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb4", + "universalIdentifier": "1244827a-ea11-532b-9722-d862948426e5", }, "bodyV2": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb5", + "universalIdentifier": "a2c8a8f0-2b25-57db-9a25-a8e1beee93a1", }, "createdAt": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb6", + "universalIdentifier": "98414831-dc54-51a4-a6a7-4144e0d124ae", }, "createdBy": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb2", + "universalIdentifier": "422888eb-d12d-5096-9b3e-7899cf4722e9", }, "dueAt": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb3", + "universalIdentifier": "92a126a5-2072-5cfd-b3e1-e0a327358056", }, "status": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb0", + "universalIdentifier": "6527420a-3345-5581-bb51-f4210f31ef3a", }, "taskTargets": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eb1", + "universalIdentifier": "2714ec81-03f5-5915-9d5f-1bec691fc1b6", }, "title": { - "universalIdentifier": "20202020-af06-4a06-8a06-ba5ca11a1eaf", + "universalIdentifier": "1480d069-9159-51ae-b316-5bc1a3f04235", }, }, }, @@ -2555,22 +2555,22 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-5a9a-44e8-95df-771cd06d0fb1", "views": { "allTaskTargets": { - "universalIdentifier": "1dbf1d24-6cca-4f55-ae2f-e3d1b425a495", + "universalIdentifier": "dea38ca0-b564-5ae1-b662-9ce388668438", "viewFields": { "id": { - "universalIdentifier": "a49287c9-8aa6-4fca-9ec5-08d643f7239f", + "universalIdentifier": "d1aa56de-7a8a-5670-9cc5-c2c159a4ff4d", }, "targetCompany": { - "universalIdentifier": "e9fa1305-4ba2-41c5-9198-fdc622b69f90", + "universalIdentifier": "47a3f1f8-e2ce-5a7c-af00-36dda03fbc44", }, "targetOpportunity": { - "universalIdentifier": "526f3354-34d6-4e7e-a870-5f99c28353c2", + "universalIdentifier": "2c09e527-cf94-5e74-9e23-fb4550c7cf18", }, "targetPerson": { - "universalIdentifier": "cadc7a33-1527-4ef8-ac00-7ed0b54d1bae", + "universalIdentifier": "611b9723-3be8-5470-8baf-5201053a38f0", }, "task": { - "universalIdentifier": "1f79839e-42f6-4a69-839a-369e21a7497d", + "universalIdentifier": "634c7721-c9d6-5ada-943f-914065531425", }, }, }, @@ -2697,49 +2697,43 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-6736-4337-b5c4-8b39fae325a5", "views": { "allTimelineActivities": { - "universalIdentifier": "20202020-b101-4b01-8b01-ba5cc01aa001", + "universalIdentifier": "28d3e8a9-e840-5034-ade2-b752bc8eb25d", "viewFields": { "happensAt": { - "universalIdentifier": "20202020-bf01-4b01-8b01-ba5cc01aa012", - }, - "linkedRecordCachedName": { - "universalIdentifier": "20202020-bf01-4b01-8b01-ba5cc01aa017", + "universalIdentifier": "3ccb9611-f9ec-56c5-82df-f12f7e26e694", }, "name": { - "universalIdentifier": "20202020-bf01-4b01-8b01-ba5cc01aa011", - }, - "properties": { - "universalIdentifier": "20202020-bf01-4b01-8b01-ba5cc01aa019", + "universalIdentifier": "821f0d94-72e0-5b04-ba97-50538f5a73c2", }, "targetCompany": { - "universalIdentifier": "2015825f-0786-4b0d-88a7-dfce1b4b1c1a", + "universalIdentifier": "d271d5b4-86ad-513d-a042-d4f8a9c41665", }, "targetDashboard": { - "universalIdentifier": "538847e8-ab09-407c-a433-505f6d7be7a1", + "universalIdentifier": "b899313f-2e84-51c2-8322-a02bb695ec91", }, "targetNote": { - "universalIdentifier": "ab74ed52-0195-4b65-987a-8367c07ee222", + "universalIdentifier": "fd6171eb-8bb7-5d3d-a7f7-bd0ebf972f90", }, "targetOpportunity": { - "universalIdentifier": "f7b5ced9-eba6-4454-8849-7a92d27c11ca", + "universalIdentifier": "a53f262e-4810-55bc-9e47-f9b7bc9a4da7", }, "targetPerson": { - "universalIdentifier": "37b38a8b-abd7-4f72-92d2-ad82bbef0296", + "universalIdentifier": "c5bd0e0d-20aa-5112-8517-df71c63e5726", }, "targetTask": { - "universalIdentifier": "3899138d-e6fa-414c-9432-214c9b797ebb", + "universalIdentifier": "3e21cd2a-77ca-5e49-90be-4532dd804c24", }, "targetWorkflow": { - "universalIdentifier": "d2c3ddc3-afad-40b9-a2cb-d2765f2f5691", + "universalIdentifier": "8e0e94b7-9c2a-5fda-bbfe-2604a0f9cbb4", }, "targetWorkflowRun": { - "universalIdentifier": "97910946-04f0-4634-804e-880bc0019225", + "universalIdentifier": "13fff327-8fbe-5a91-8d95-4a9ca455cfe9", }, "targetWorkflowVersion": { - "universalIdentifier": "4a7e3213-afd5-4691-8bba-0a10e8697afb", + "universalIdentifier": "c4aa8b50-d9bb-5395-a90a-fa0d36312634", }, "workspaceMember": { - "universalIdentifier": "20202020-bf01-4b01-8b01-ba5cc01aa013", + "universalIdentifier": "09d2d7d9-6809-561b-9dc7-1588b6a18673", }, }, }, @@ -2807,25 +2801,25 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-62be-406c-b9ca-8caa50d51392", "views": { "allWorkflows": { - "universalIdentifier": "20202020-a009-4a09-8a09-a0bcf10aa11a", + "universalIdentifier": "84dddf6b-edfa-55c4-ab1d-886602c1ca38", "viewFields": { "createdBy": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11d", + "universalIdentifier": "982cf2c6-a9a2-5557-9733-e3124168ee9f", }, "name": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11a", + "universalIdentifier": "d0c96ceb-e6f7-562d-b859-35341481cdf2", }, "runs": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11f", + "universalIdentifier": "869394ba-f9b4-5ce2-8312-b9a9827baea8", }, "statuses": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11b", + "universalIdentifier": "47e57811-bc0b-5b77-9936-384590672b71", }, "updatedAt": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11c", + "universalIdentifier": "22bc20fd-c132-50e0-9bff-b0a405e729db", }, "versions": { - "universalIdentifier": "20202020-af09-4a09-8a09-a0bcf10aa11e", + "universalIdentifier": "4978149a-4ed7-5d59-aafb-2dd69b6cd6fe", }, }, }, @@ -2875,16 +2869,16 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-3319-4234-a34c-7f3b9d2e4d1f", "views": { "allWorkflowAutomatedTriggers": { - "universalIdentifier": "a0a9ef79-3d42-417a-8555-3ee54c18ea51", + "universalIdentifier": "d53c1b59-4f9c-5647-aaf5-ac8197ca1c3f", "viewFields": { "createdAt": { - "universalIdentifier": "bb35e66a-2a1e-416b-8105-5749d91ab65f", + "universalIdentifier": "5897f02c-e29d-5085-9bbe-60c48a8360b5", }, "type": { - "universalIdentifier": "689b4749-aa40-489a-bf0b-475a197ca2e6", + "universalIdentifier": "f4456853-dad8-57e8-81a4-6987477a40f7", }, "workflow": { - "universalIdentifier": "e5a46195-06fe-4f47-8844-128e35151d37", + "universalIdentifier": "53a781ed-4cd6-58fd-8de9-26e5cc7f01a7", }, }, }, @@ -2986,25 +2980,16 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-4e28-4e95-a9d7-6c00874f843c", "views": { "allWorkflowRuns": { - "universalIdentifier": "20202020-a011-4a11-8a11-a0bcf10abca5", + "universalIdentifier": "7f1e3269-5de1-56e9-b89f-4cccaf4d53d6", "viewFields": { - "createdBy": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcb3", - }, "name": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcaf", - }, - "startedAt": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcb2", + "universalIdentifier": "b1ae1446-834a-54bb-b995-04644ee4f0cf", }, "status": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcb1", + "universalIdentifier": "f62369bb-a4cc-5d88-ac8e-7740ea318837", }, "workflow": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcb0", - }, - "workflowVersion": { - "universalIdentifier": "20202020-af11-4a11-8a11-a0bcf10abcb4", + "universalIdentifier": "1c323f33-aa9a-5975-8a60-095817bf6412", }, }, }, @@ -3121,22 +3106,22 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-d65d-4ab9-9344-d77bfb376a3d", "views": { "allWorkflowVersions": { - "universalIdentifier": "20202020-a010-4a10-8a10-a0bcf10aae15", + "universalIdentifier": "a305b346-a83c-5bdd-880b-7a8e71310bf3", "viewFields": { "name": { - "universalIdentifier": "20202020-af10-4a10-8a10-a0bcf10aaeaf", + "universalIdentifier": "84a34cf6-7fd8-5972-894d-bc21e026f334", }, "runs": { - "universalIdentifier": "20202020-af10-4a10-8a10-a0bcf10aaeb3", + "universalIdentifier": "255ab204-2ebe-582c-bfbd-4a846490d721", }, "status": { - "universalIdentifier": "20202020-af10-4a10-8a10-a0bcf10aaeb1", + "universalIdentifier": "6ec4080f-d57e-5837-8f99-b36cebbb2fde", }, "updatedAt": { - "universalIdentifier": "20202020-af10-4a10-8a10-a0bcf10aaeb2", + "universalIdentifier": "604b9aaa-ed58-5e44-869a-0e6402c00c64", }, "workflow": { - "universalIdentifier": "20202020-af10-4a10-8a10-a0bcf10aaeb0", + "universalIdentifier": "9c66dd1e-1b6d-5ad9-9e81-25c4c0c20799", }, }, }, @@ -3280,40 +3265,19 @@ exports[`STANDARD_OBJECTS universal identifiers should never change without an e "universalIdentifier": "20202020-3319-4234-a34c-82d5c0e881a6", "views": { "allWorkspaceMembers": { - "universalIdentifier": "20202020-e001-4e01-8e01-a0bcaeabe100", + "universalIdentifier": "55546e30-498e-51f8-bcf2-14617c1fd8aa", "viewFields": { "assignedTasks": { - "universalIdentifier": "af16226e-6375-4676-8bd9-9d1a57076fc4", - }, - "avatarUrl": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f2", - }, - "colorScheme": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f3", + "universalIdentifier": "d401db50-821d-540d-852f-d5ee06d7b2ed", }, "createdAt": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f8", - }, - "dateFormat": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f6", - }, - "locale": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f4", + "universalIdentifier": "03129632-0c81-576f-884f-65d366c2ca3a", }, "name": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f0", + "universalIdentifier": "7af9ed3a-b187-5dad-b8c6-451b50fed4d8", }, "ownedOpportunities": { - "universalIdentifier": "8a0503f3-ba61-453e-86dc-6c79f7bc235b", - }, - "timeFormat": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f7", - }, - "timeZone": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f5", - }, - "userEmail": { - "universalIdentifier": "20202020-ef01-4e01-8e01-a0bcaeabe1f1", + "universalIdentifier": "e15281a1-9f03-5b59-87d9-7f6a5b7a8d99", }, }, }, diff --git a/packages/twenty-shared/src/metadata/constants/standard-object-fields.constant.ts b/packages/twenty-shared/src/metadata/constants/standard-object-fields.constant.ts new file mode 100644 index 0000000000..8a58d4283d --- /dev/null +++ b/packages/twenty-shared/src/metadata/constants/standard-object-fields.constant.ts @@ -0,0 +1,1088 @@ +import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from '@/application/constants/TwentyStandardApplicationUniversalIdentifier'; +import { getSystemRelationFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-relation-field-universal-identifier.util'; +import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from '@/metadata/constants/standard-object-universal-identifiers.constant'; +import { buildStandardObjectSystemFields } from '@/metadata/utils/internal/build-standard-object-system-fields.util'; + +// Important notice: +// - Never ever mutate an existing universal identifier +// - Deleting an existing universal identifier should be very rare +// - System field universal identifiers (id, createdAt, updatedAt, deletedAt, +// createdBy, updatedBy, position, searchVector) are deterministically derived +// from the standard application universal identifier, the object universal +// identifier and the field name (buildStandardObjectSystemFields). The name +// field is a default field, not a system field, and keeps its hardcoded +// universal identifier. +// - System relation field universal identifiers are deterministically derived +// from the object + the relation target object +// (getSystemRelationFieldUniversalIdentifier). +// +// Fields live in their own const so that both STANDARD_OBJECTS' `fields` and +// its INDEX view (buildStandardObjectIndexView) can read the same field +// universal identifiers. +export const STANDARD_OBJECT_FIELDS = { + timelineActivity: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + ), + name: { universalIdentifier: '20202020-7207-46e8-9dab-849505ae8497' }, + happensAt: { + universalIdentifier: '20202020-9526-4993-b339-c4318c4d39f0', + }, + properties: { + universalIdentifier: '20202020-f142-4b04-b91b-6a2b4af3bf11', + }, + workspaceMember: { + universalIdentifier: '20202020-af23-4479-9a30-868edc474b36', + }, + targetPerson: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + }), + }, + targetCompany: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + }), + }, + targetOpportunity: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + }), + }, + targetTask: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + }), + }, + targetNote: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + }), + }, + targetWorkflow: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + }), + }, + targetWorkflowVersion: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, + }), + }, + targetWorkflowRun: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, + }), + }, + targetDashboard: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + }), + }, + targetMessageList: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, + }), + }, + targetMessageCampaign: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, + }), + }, + linkedRecordCachedName: { + universalIdentifier: '20202020-cfdb-4bef-bbce-a29f41230934', + }, + linkedRecordId: { + universalIdentifier: '20202020-2e0e-48c0-b445-ee6c1e61687d', + }, + linkedObjectMetadataId: { + universalIdentifier: '20202020-c595-449d-9f89-562758c9ee69', + }, + }, + attachment: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + ), + name: { universalIdentifier: '20202020-87a5-48f8-bbf7-ade388825a57' }, + file: { universalIdentifier: '20202020-15db-460e-8166-c7b5d87ad4be' }, + //deprecated + fullPath: { universalIdentifier: '20202020-0d19-453d-8e8d-fbcda8ca3747' }, + //deprecated + fileCategory: { + universalIdentifier: '20202020-8c3f-4d9e-9a1b-2e5f7a8c9d0e', + }, + targetTask: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + }), + }, + targetNote: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + }), + }, + targetPerson: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + }), + }, + targetCompany: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + }), + }, + targetOpportunity: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + }), + }, + targetDashboard: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + }), + }, + targetWorkflow: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + }), + }, + }, + blocklist: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.blocklist, + ), + handle: { universalIdentifier: '20202020-eef3-44ed-aa32-4641d7fd4a3e' }, + workspaceMember: { + universalIdentifier: '20202020-548d-4084-a947-fa20a39f7c06', + }, + }, + calendarChannelEventAssociation: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarChannelEventAssociation, + ), + calendarChannelId: { + universalIdentifier: '20202020-93ee-4da4-8d58-0282c4a9cb7d', + }, + calendarEvent: { + universalIdentifier: '20202020-5aa5-437e-bb86-f42d457783e3', + }, + eventExternalId: { + universalIdentifier: '20202020-9ec8-48bb-b279-21d0734a75a1', + }, + recurringEventExternalId: { + universalIdentifier: '20202020-c58f-4c69-9bf8-9518fa31aa50', + }, + }, + calendarEventParticipant: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEventParticipant, + ), + calendarEvent: { + universalIdentifier: '20202020-fe3a-401c-b889-af4f4657a861', + }, + handle: { + universalIdentifier: '20202020-8692-4580-8210-9e09cbd031a7', + }, + displayName: { + universalIdentifier: '20202020-ee1e-4f9f-8ac1-5c0b2f69691e', + }, + isOrganizer: { + universalIdentifier: '20202020-66e7-4e00-9e06-d06c92650580', + }, + responseStatus: { + universalIdentifier: '20202020-cec0-4be8-8fba-c366abc23147', + }, + person: { + universalIdentifier: '20202020-5761-4842-8186-e1898ef93966', + }, + workspaceMember: { + universalIdentifier: '20202020-20e4-4591-93ed-aeb17a4dcbd2', + }, + }, + calendarEvent: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent, + ), + title: { universalIdentifier: '20202020-080e-49d1-b21d-9702a7e2525c' }, + isCanceled: { + universalIdentifier: '20202020-335b-4e04-b470-43b84b64863c', + }, + isFullDay: { + universalIdentifier: '20202020-551c-402c-bb6d-dfe9efe86bcb', + }, + startsAt: { + universalIdentifier: '20202020-2c57-4c75-93c5-2ac950a6ed67', + }, + endsAt: { universalIdentifier: '20202020-2554-4ee1-a617-17907f6bab21' }, + externalCreatedAt: { + universalIdentifier: '20202020-9f03-4058-a898-346c62181599', + }, + externalUpdatedAt: { + universalIdentifier: '20202020-b355-4c18-8825-ef42c8a5a755', + }, + description: { + universalIdentifier: '20202020-52c4-4266-a98f-e90af0b4d271', + }, + location: { + universalIdentifier: '20202020-641a-4ffe-960d-c3c186d95b17', + }, + iCalUid: { + universalIdentifier: '20202020-f24b-45f4-b6a3-d2f9fcb98714', + }, + conferenceSolution: { + universalIdentifier: '20202020-1c3f-4b5a-b526-5411a82179eb', + }, + conferenceLink: { + universalIdentifier: '20202020-35da-43ef-9ca0-e936e9dc237b', + }, + calendarChannelEventAssociations: { + universalIdentifier: '20202020-bdf8-4572-a2cc-ecbb6bcc3a02', + }, + calendarEventParticipants: { + universalIdentifier: '20202020-e07e-4ccb-88f5-6f3d00458eec', + }, + callRecordings: { + universalIdentifier: '48d6d151-18e2-4111-b405-d85fb9d860d8', + }, + }, + callRecording: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.callRecording, + ), + title: { + universalIdentifier: '4cff8863-a1d1-45fd-a370-4eb6aa1f2a5b', + }, + status: { + universalIdentifier: '3e617680-d93e-4309-a54f-90f69528bfd7', + }, + recordingRequestStatus: { + universalIdentifier: '7fd681c9-244c-4e98-8939-7b175d472638', + }, + applicationId: { + universalIdentifier: '24ec1239-1240-42cb-8a2d-302632378e09', + }, + externalBotId: { + universalIdentifier: '0a2da128-9bcc-488b-bc31-65318c41bdf9', + }, + externalRecordingId: { + universalIdentifier: '6d17fb71-324b-4625-a5be-b3580607e2c7', + }, + startedAt: { + universalIdentifier: '6c56c23f-1987-410a-860a-df3b2b3f9a33', + }, + endedAt: { + universalIdentifier: '7a38a9cf-8424-4d6e-b80a-6883d3c662ef', + }, + video: { + universalIdentifier: 'bb9523d3-457e-4f4b-8c79-27a77afb87da', + }, + audio: { + universalIdentifier: '2eafc2d0-8fec-430c-a939-65ca5fbc0f08', + }, + transcript: { + universalIdentifier: '27b86d68-57d1-4607-aca0-191896b1ad43', + }, + summary: { + universalIdentifier: 'adb0f472-756b-4d3f-b21e-ea32bf73a5e4', + }, + calendarEvent: { + universalIdentifier: '49e64b28-bd98-4775-80ea-4781bdd45e35', + }, + }, + company: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + ), + name: { universalIdentifier: '20202020-4d99-4e2e-a84c-4a27837b1ece' }, + domainName: { + universalIdentifier: '20202020-0c28-43d8-8ba5-3659924d3489', + }, + address: { universalIdentifier: '20202020-c5ce-4adc-b7b6-9c0979fc55e7' }, + linkedinLink: { + universalIdentifier: '20202020-ebeb-4beb-b9ad-6848036fb451', + }, + annualRevenue: { + universalIdentifier: '60f533b7-2166-4071-a767-ceb0286822fd', + }, + people: { universalIdentifier: '20202020-3213-4ddf-9494-6422bcff8d7c' }, + accountOwner: { + universalIdentifier: '20202020-95b8-4e10-9881-edb5d4765f9d', + }, + taskTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + }), + }, + noteTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + }), + }, + opportunities: { + universalIdentifier: '20202020-add3-4658-8e23-d70dccb6d0ec', + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + dashboard: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + ), + title: { universalIdentifier: '20202020-20ee-4091-95dc-44b57eda3a89' }, + pageLayoutId: { + universalIdentifier: '20202020-bb53-4648-aa36-1d9d54e6f7f2', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + }, + messageCampaign: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, + ), + name: { universalIdentifier: 'dc8bca0e-37c4-4b78-814d-2e91124274e3' }, + subject: { universalIdentifier: '7251544c-b07a-4f0d-9d0a-48514367f230' }, + bodyTemplate: { + universalIdentifier: 'b3a69d08-31ca-4a8d-8359-5ca462899342', + }, + fromAddress: { + universalIdentifier: '91e1a33c-c1ff-411a-b720-9085e13c05db', + }, + status: { universalIdentifier: 'c7117256-3de6-48e1-87df-c99c32bad610' }, + sentAt: { universalIdentifier: 'e2315b4f-9edf-4df2-96b9-961e76368671' }, + sentCount: { + universalIdentifier: '2f333d2b-37b8-4ddc-ad0d-c07c6ce066ad', + }, + failedCount: { + universalIdentifier: 'd373fcd7-b1ce-4c77-8031-a5785c475028', + }, + bouncedCount: { + universalIdentifier: '20d884a9-34dd-4667-8ecb-ceec224258e2', + }, + complainedCount: { + universalIdentifier: '82842cfa-f12a-4bab-bbde-b2cf587d0406', + }, + unsubscribeTopicId: { + universalIdentifier: '0648e7ad-1769-4ff6-a4d5-72da79ef169c', + }, + list: { universalIdentifier: 'cb24dcdf-f0e8-4c71-8cff-70b714e86530' }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + messages: { universalIdentifier: 'e5a177a7-512b-4778-928e-69777a528f7c' }, + recipients: { + universalIdentifier: '05a3271c-5b91-493c-8f30-2d27b31d019e', + }, + }, + messageList: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, + ), + name: { universalIdentifier: '69b9ed8b-7b26-4108-894f-05700ef7e8ee' }, + members: { + universalIdentifier: '92df3493-91cf-4665-8587-1b08917d299b', + }, + campaigns: { + universalIdentifier: 'e098d838-31ab-4812-91a8-f055f45a6832', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + messageListMember: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageListMember, + ), + person: { universalIdentifier: '34288425-8805-42fb-8b98-ee13d09be3d3' }, + list: { + universalIdentifier: 'd5402005-e8f9-4fbe-8696-b6723cd85018', + }, + }, + messageChannelMessageAssociation: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociation, + ), + messageChannelId: { + universalIdentifier: '20202020-b658-408f-bd46-3bd2d15d7e52', + }, + message: { + universalIdentifier: '20202020-da5d-4ac5-8743-342ab0a0336b', + }, + messageExternalId: { + universalIdentifier: '20202020-37d6-438f-b6fd-6503596c8f34', + }, + messageThread: { + universalIdentifier: '20202020-fac8-42a8-94dd-44dbc920ae16', + }, + messageThreadExternalId: { + universalIdentifier: '20202020-35fb-421e-afa0-0b8e8f7f9018', + }, + direction: { + universalIdentifier: '75c9b0f7-9e76-44d4-a2f9-47051e61eec7', + }, + messageFolders: { + universalIdentifier: '9bfc9da7-ae2d-44fd-9563-ede90c5d6222', + }, + }, + messageChannelMessageAssociationMessageFolder: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociationMessageFolder, + ), + messageChannelMessageAssociation: { + universalIdentifier: '7411cfa3-4fd9-4b90-a636-940015fd7243', + }, + messageFolderId: { + universalIdentifier: 'b3369d31-3856-4a7a-b007-ee353918127c', + }, + }, + messageParticipant: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageParticipant, + ), + message: { + universalIdentifier: '20202020-985b-429a-9db9-9e55f4898a2a', + }, + role: { + universalIdentifier: '20202020-65d1-42f4-8729-c9ec1f52aecd', + }, + handle: { + universalIdentifier: '20202020-2456-464e-b422-b965a4db4a0b', + }, + displayName: { + universalIdentifier: '20202020-36dd-4a4f-ac02-228425be9fac', + }, + person: { + universalIdentifier: '20202020-249d-4e0f-82cd-1b9df5cd3da2', + }, + workspaceMember: { + universalIdentifier: '20202020-77a7-4845-99ed-1bcbb478be6f', + }, + messageCampaign: { + universalIdentifier: '5bc768db-919f-41da-8c43-df08084d526f', + }, + }, + messageThread: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageThread, + ), + messages: { + universalIdentifier: '20202020-3115-404f-aade-e1154b28e35a', + }, + messageChannelMessageAssociations: { + universalIdentifier: '20202020-314e-40a4-906d-a5d5d6c285f6', + }, + subject: { + universalIdentifier: 'a8ddbf8c-1137-45d1-b89e-5ffbd83f67c8', + }, + }, + message: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.message, + ), + headerMessageId: { + universalIdentifier: '20202020-72b5-416d-aed8-b55609067d01', + }, + messageThread: { + universalIdentifier: '20202020-30f2-4ccd-9f5c-e41bb9d26214', + }, + subject: { universalIdentifier: '20202020-52d1-4036-b9ae-84bd722bb37a' }, + text: { universalIdentifier: '20202020-d2ee-4e7e-89de-9a0a9044a143' }, + receivedAt: { + universalIdentifier: '20202020-140a-4a2a-9f86-f13b6a979afc', + }, + messageParticipants: { + universalIdentifier: '20202020-7cff-4a74-b63c-73228448cbd9', + }, + messageChannelMessageAssociations: { + universalIdentifier: '20202020-3cef-43a3-82c6-50e7cfbc9ae4', + }, + messageCampaign: { + universalIdentifier: '77cff00b-a0ba-48d6-80de-0d5ccf14e45b', + }, + deliveryStatus: { + universalIdentifier: '209254fa-2b89-429d-a72a-c401c4bd5a78', + }, + isDraft: { + universalIdentifier: '20202020-4d3a-4b6e-9c1f-2a5e7b9d0c34', + }, + }, + note: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + ), + title: { universalIdentifier: '20202020-faeb-4c76-8ba6-ccbb0b4a965f' }, + bodyV2: { universalIdentifier: '20202020-a7bb-4d94-be51-8f25181502c8' }, + noteTargets: { + universalIdentifier: '20202020-1f25-43fe-8b00-af212fdde823', + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + noteTarget: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + ), + note: { universalIdentifier: '20202020-57f3-4f50-9599-fc0f671df003' }, + targetPerson: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + }), + }, + targetCompany: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + }), + }, + targetOpportunity: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + }), + }, + }, + opportunity: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + ), + name: { universalIdentifier: '20202020-8609-4f65-a2d9-44009eb422b5' }, + amount: { universalIdentifier: '20202020-583e-4642-8533-db761d5fa82f' }, + closeDate: { + universalIdentifier: '20202020-527e-44d6-b1ac-c4158d307b97', + }, + stage: { universalIdentifier: '20202020-6f76-477d-8551-28cd65b2b4b9' }, + pointOfContact: { + universalIdentifier: '20202020-8dfb-42fc-92b6-01afb759ed16', + }, + company: { universalIdentifier: '20202020-cbac-457e-b565-adece5fc815f' }, + owner: { universalIdentifier: '20202020-be7e-4d1e-8e19-3d5c7c4b9f2a' }, + taskTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + }), + }, + noteTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + }), + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + person: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + ), + name: { universalIdentifier: '20202020-3875-44d5-8c33-a6239011cab8' }, + emails: { universalIdentifier: '20202020-3c51-43fa-8b6e-af39e29368ab' }, + linkedinLink: { + universalIdentifier: '20202020-f1af-48f7-893b-2007a73dd508', + }, + jobTitle: { universalIdentifier: '20202020-b0d0-415a-bef9-640a26dacd9b' }, + phones: { universalIdentifier: '20202020-0638-448e-8825-439134618022' }, + avatarUrl: { + universalIdentifier: '20202020-b8a6-40df-961c-373dc5d2ec21', + }, + avatarFile: { + universalIdentifier: '20202020-a7c9-4e3d-8f1b-2d5a6b7c8e9f', + }, + company: { universalIdentifier: '20202020-e2f3-448e-b34c-2d625f0025fd' }, + pointOfContactForOpportunities: { + universalIdentifier: '20202020-911b-4a7d-b67b-918aa9a5b33a', + }, + taskTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + }), + }, + noteTargets: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + }), + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + messageParticipants: { + universalIdentifier: '20202020-498e-4c61-8158-fa04f0638334', + }, + calendarEventParticipants: { + universalIdentifier: '20202020-52ee-45e9-a702-b64b3753e3a9', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + listMemberships: { + universalIdentifier: '8b8d1be0-4c94-4413-a2c9-c7ede205a81d', + }, + }, + task: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + ), + title: { universalIdentifier: '20202020-b386-4cb7-aa5a-08d4a4d92680' }, + bodyV2: { universalIdentifier: '20202020-4aa0-4ae8-898d-7df0afd47ab1' }, + dueAt: { universalIdentifier: '20202020-fd99-40da-951b-4cb9a352fce3' }, + status: { universalIdentifier: '20202020-70bc-48f9-89c5-6aa730b151e0' }, + taskTargets: { + universalIdentifier: '20202020-de9c-4d0e-a452-713d4a3e5fc7', + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + assignee: { universalIdentifier: '20202020-065a-4f42-a906-e20422c1753f' }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + taskTarget: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + ), + task: { universalIdentifier: '20202020-e881-457a-8758-74aaef4ae78a' }, + targetPerson: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + }), + }, + targetCompany: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + }), + }, + targetOpportunity: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + }), + }, + }, + workflow: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + ), + name: { universalIdentifier: '20202020-b3d3-478f-acc0-5d901e725b20' }, + lastPublishedVersionId: { + universalIdentifier: '20202020-326a-4fba-8639-3456c0a169e8', + }, + coreWorkflowId: { + universalIdentifier: '20202020-058a-42ad-8eb8-0662a5552aad', + }, + statuses: { universalIdentifier: '20202020-357c-4432-8c50-8c31b4a552d9' }, + versions: { universalIdentifier: '20202020-9432-416e-8f3c-27ee3153d099' }, + runs: { universalIdentifier: '20202020-759b-4340-b58b-e73595c4df4f' }, + automatedTriggers: { + universalIdentifier: '20202020-3319-4234-a34c-117ecad2b8a9', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + attachments: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + }), + }, + }, + workflowAutomatedTrigger: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowAutomatedTrigger, + ), + type: { + universalIdentifier: '20202020-3319-4234-a34c-3f92c1ab56e7', + }, + settings: { + universalIdentifier: '20202020-3319-4234-a34c-bac8f903de12', + }, + workflow: { + universalIdentifier: '20202020-3319-4234-a34c-8e1a4d2f7c03', + }, + }, + workflowRun: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, + ), + name: { universalIdentifier: '20202020-b840-4253-aef9-4e5013694587' }, + workflowVersion: { + universalIdentifier: '20202020-2f52-4ba8-8dc4-d0d6adb9578d', + }, + workflow: { + universalIdentifier: '20202020-8c57-4e7f-84f5-f373f68e1b82', + }, + enqueuedAt: { + universalIdentifier: '20202020-f1e3-4de1-a461-b5c4fdbc861d', + }, + startedAt: { + universalIdentifier: '20202020-a234-4e2d-bd15-85bcea6bb183', + }, + endedAt: { universalIdentifier: '20202020-e1c1-4b6b-bbbd-b2beaf2e159e' }, + status: { universalIdentifier: '20202020-6b3e-4f9c-8c2b-2e5b8e6d6f3b' }, + state: { universalIdentifier: '20202020-611f-45f3-9cde-d64927e8ec57' }, + stepLogs: { + universalIdentifier: '20202020-7c4e-4e1a-8fc1-1e3a55d6c2a1', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + workflowVersion: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, + ), + name: { universalIdentifier: '20202020-a12f-4cca-9937-a2e40cc65509' }, + workflow: { + universalIdentifier: '20202020-afa3-46c3-91b0-0631ca6aa1c8', + }, + trigger: { + universalIdentifier: '20202020-4eae-43e7-86e0-212b41a30b48', + }, + status: { + universalIdentifier: '20202020-5a34-440e-8a25-39d8c3d1d4cf', + }, + runs: { universalIdentifier: '20202020-1d08-46df-901a-85045f18099a' }, + steps: { universalIdentifier: '20202020-5988-4a64-b94a-1f9b7b989039' }, + coreWorkflowVersionId: { + universalIdentifier: '20202020-58b4-46e8-b6d2-f1f3c74cf7f4', + }, + timelineActivities: { + universalIdentifier: getSystemRelationFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, + relationTargetObjectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + }), + }, + }, + workspaceMember: { + ...buildStandardObjectSystemFields( + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember, + ), + name: { universalIdentifier: '20202020-e914-43a6-9c26-3603c59065f4' }, + colorScheme: { + universalIdentifier: '20202020-66bc-47f2-adac-f2ef7c598b63', + }, + locale: { + universalIdentifier: '20202020-402e-4695-b169-794fa015afbe', + }, + avatarUrl: { + universalIdentifier: '20202020-0ced-4c4f-a376-c98a966af3f6', + }, + userEmail: { + universalIdentifier: '20202020-4c5f-4e09-bebc-9e624e21ecf4', + }, + jobTitle: { + universalIdentifier: '20202020-4dd4-4619-826e-08f6c06b374d', + }, + userId: { + universalIdentifier: '20202020-75a9-4dfc-bf25-2e4b43e89820', + }, + assignedTasks: { + universalIdentifier: '20202020-61dc-4a1c-99e8-38ebf8d2bbeb', + }, + ownedOpportunities: { + universalIdentifier: '20202020-9e4d-4b3a-8c1f-6d7e8f9a0b1c', + }, + accountOwnerForCompanies: { + universalIdentifier: '20202020-dc29-4bd4-a3c1-29eafa324bee', + }, + messageParticipants: { + universalIdentifier: '20202020-8f99-48bc-a5eb-edd33dd54188', + }, + blocklist: { + universalIdentifier: '20202020-6cb2-4161-9f29-a4b7f1283859', + }, + calendarEventParticipants: { + universalIdentifier: '20202020-0dbc-4841-9ce1-3e793b5b3512', + }, + timelineActivities: { + universalIdentifier: '20202020-e15b-47b8-94fe-8200e3c66615', + }, + timeZone: { + universalIdentifier: '20202020-2d33-4c21-a86e-5943b050dd54', + }, + dateFormat: { + universalIdentifier: '20202020-af13-4e11-b1e7-b8cf5ea13dc0', + }, + timeFormat: { + universalIdentifier: '20202020-8acb-4cf8-a851-a6ed443c8d81', + }, + calendarStartDay: { + universalIdentifier: '20202020-1ecc-4562-84c9-ff3a2f6cce85', + }, + numberFormat: { + universalIdentifier: '20202020-7f40-4e7f-b126-11c0eda6b141', + }, + }, +} satisfies Record>; diff --git a/packages/twenty-shared/src/metadata/constants/standard-object-universal-identifiers.constant.ts b/packages/twenty-shared/src/metadata/constants/standard-object-universal-identifiers.constant.ts index edee295d6a..13f4cb6b35 100644 --- a/packages/twenty-shared/src/metadata/constants/standard-object-universal-identifiers.constant.ts +++ b/packages/twenty-shared/src/metadata/constants/standard-object-universal-identifiers.constant.ts @@ -1,11 +1,17 @@ -// Object universal identifiers referenced from another object's declaration -// (default relation builders need both the host and the source object -// identifiers, and an object literal cannot reference its sibling keys). -// Each STANDARD_OBJECTS entry involved in the default relations reads its own -// universalIdentifier from this record so there is a single source of truth. +// Single source of truth for standard object universal identifiers: an object +// identifier is referenced from its own STANDARD_OBJECTS entry, from its +// STANDARD_OBJECT_FIELDS entry (system fields and INDEX view derivation), and +// sometimes from another object's declaration (default relation builders need +// both the host and the source object identifiers, and an object literal +// cannot reference its sibling keys). export const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = { timelineActivity: '20202020-6736-4337-b5c4-8b39fae325a5', attachment: '20202020-bd3d-4c60-8dca-571c71d4447a', + blocklist: '20202020-0408-4f38-b8a8-4d5e3e26e24d', + calendarChannelEventAssociation: '20202020-491b-4aaa-9825-afd1bae6ae00', + calendarEvent: '20202020-8f1d-4eef-9f85-0d1965e27221', + calendarEventParticipant: '20202020-a1c3-47a6-9732-27e5b1e8436d', + callRecording: 'ce19efb9-710f-45b2-b141-473abbeea60b', noteTarget: '20202020-fff0-4b44-be82-bda313884400', taskTarget: '20202020-5a9a-44e8-95df-771cd06d0fb1', person: '20202020-e674-48e5-a542-72570eee7213', @@ -14,11 +20,20 @@ export const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = { note: '20202020-0b00-45cd-b6f6-6cd806fc6804', task: '20202020-1ba1-48ba-bc83-ef7e5990ed10', workflow: '20202020-62be-406c-b9ca-8caa50d51392', + workflowAutomatedTrigger: '20202020-3319-4234-a34c-7f3b9d2e4d1f', workflowVersion: '20202020-d65d-4ab9-9344-d77bfb376a3d', workflowRun: '20202020-4e28-4e95-a9d7-6c00874f843c', + workspaceMember: '20202020-3319-4234-a34c-82d5c0e881a6', dashboard: '20202020-3840-4b6d-9425-0c5188b05ca8', + message: '20202020-3f6b-4425-80ab-e468899ab4b2', + messageChannelMessageAssociation: '20202020-ad1e-4127-bccb-d83ae04d2ccb', + messageChannelMessageAssociationMessageFolder: + '20202020-a1b0-40b0-8ab0-5b6c7d8e9f0a', messageList: '826561ea-4816-411c-baa0-eec5e6ca8866', + messageListMember: '27773d24-8ce3-40f8-aa6c-1f590f2c08d2', messageCampaign: '238acb94-dd4c-4036-bc55-19b99d821efd', + messageParticipant: '20202020-a433-4456-aa2d-fd9cb26b774a', + messageThread: '20202020-849a-4c3e-84f5-a25a7d802271', } as const; export type StandardObjectWithUniversalIdentifierName = 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 db0c756a08..90d9f8063d 100644 --- a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts +++ b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts @@ -1,103 +1,22 @@ -import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from '@/application/constants/TwentyStandardApplicationUniversalIdentifier'; -import { getSystemRelationFieldUniversalIdentifier } from '@/application/deterministic-identifier/get-system-relation-field-universal-identifier.util'; +import { STANDARD_OBJECT_FIELDS } from '@/metadata/constants/standard-object-fields.constant'; import { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from '@/metadata/constants/standard-object-universal-identifiers.constant'; -import { buildStandardObjectSystemFields } from '@/metadata/utils/internal/build-standard-object-system-fields.util'; +import { buildStandardObjectIndexView } from '@/metadata/utils/internal/build-standard-object-index-view.util'; // Important notice: // - Never ever mutate an existing universal identifier // - Deleting an existing universal identifier should be very rare -// - System field universal identifiers (id, createdAt, updatedAt, -// deletedAt, createdBy, updatedBy, position, searchVector) are -// deterministically derived from the standard application universal -// identifier, the object universal identifier and the field name. -// The name field is a default field, not a system field, and keeps its -// hardcoded universal identifier. +// - Field universal identifiers live in STANDARD_OBJECT_FIELDS (see +// standard-object-fields.constant.ts), so both an object's `fields` and its +// INDEX view can read the same values. +// - INDEX view universal identifiers (the "All {objectLabelPlural}" table view +// keyed on ViewKey.INDEX) and their view-field universal identifiers are +// deterministically derived by buildStandardObjectIndexView +// (getSystemViewUniversalIdentifier for the view, +// getViewFieldUniversalIdentifier for each view field). export const STANDARD_OBJECTS = { attachment: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - ), - name: { universalIdentifier: '20202020-87a5-48f8-bbf7-ade388825a57' }, - file: { universalIdentifier: '20202020-15db-460e-8166-c7b5d87ad4be' }, - //deprecated - fullPath: { universalIdentifier: '20202020-0d19-453d-8e8d-fbcda8ca3747' }, - //deprecated - fileCategory: { - universalIdentifier: '20202020-8c3f-4d9e-9a1b-2e5f7a8c9d0e', - }, - targetTask: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - }), - }, - targetNote: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - }), - }, - targetPerson: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - }), - }, - targetCompany: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - }), - }, - targetOpportunity: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - }), - }, - targetDashboard: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - }), - }, - targetWorkflow: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.attachment, morphIds: { targetMorphId: { morphId: '20202020-f634-435d-ab8d-e1168b375c69' }, }, @@ -125,77 +44,41 @@ export const STANDARD_OBJECTS = { }, }, views: { - allAttachments: { - universalIdentifier: '3f7f3363-7087-44cc-902d-5e8904262316', - viewFields: { - name: { - universalIdentifier: 'be56712f-d7a6-4fbe-b92b-d750f0708a0a', - }, - file: { - universalIdentifier: '873cf114-5477-4b62-9023-7ea6ad69fbe5', - }, - createdBy: { - universalIdentifier: 'fa363372-0fdf-4bb3-bdf1-0ead354b9225', - }, - createdAt: { - universalIdentifier: '6c092c26-b1cb-488f-ae2e-5af4bec1162b', - }, - targetPerson: { - universalIdentifier: '73a4c3a7-c7f9-4ed6-a2b6-117d7efad0f3', - }, - targetCompany: { - universalIdentifier: 'b335ad04-059e-4c36-8666-f40431849044', - }, - targetOpportunity: { - universalIdentifier: '15f2d457-dc09-4c52-bf2a-47083d6bf017', - }, - targetTask: { - universalIdentifier: 'c2913c5e-6cc6-438d-9c2f-3212a9b2a82b', - }, - targetNote: { - universalIdentifier: 'fc8dba49-bcf2-41b8-a435-0c4a3bbf2af6', - }, - targetDashboard: { - universalIdentifier: 'bcc6d6e1-7c0b-4291-9270-66e42024d8dd', - }, - targetWorkflow: { - universalIdentifier: '11fcf58b-dbab-42dd-be67-689462111070', - }, - }, - }, + allAttachments: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, + fields: STANDARD_OBJECT_FIELDS.attachment, + viewFieldNames: [ + 'name', + 'file', + 'createdBy', + 'createdAt', + 'targetPerson', + 'targetCompany', + 'targetOpportunity', + 'targetTask', + 'targetNote', + 'targetDashboard', + 'targetWorkflow', + ], + }), }, }, blocklist: { - universalIdentifier: '20202020-0408-4f38-b8a8-4d5e3e26e24d', - fields: { - ...buildStandardObjectSystemFields( - '20202020-0408-4f38-b8a8-4d5e3e26e24d', - ), - handle: { universalIdentifier: '20202020-eef3-44ed-aa32-4641d7fd4a3e' }, - workspaceMember: { - universalIdentifier: '20202020-548d-4084-a947-fa20a39f7c06', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.blocklist, + fields: STANDARD_OBJECT_FIELDS.blocklist, indexes: { workspaceMemberIdIndex: { universalIdentifier: '4daf320e-74d0-4f24-a45a-af3a09d741cb', }, }, views: { - allBlocklists: { - universalIdentifier: '5a98e88c-67c2-4f61-a5ab-a0d3d6a836bb', - viewFields: { - handle: { - universalIdentifier: '155ae00d-0def-4f62-9473-8a8efa209eee', - }, - workspaceMember: { - universalIdentifier: '05a2f0b9-f2ef-4729-bc42-9e2ad2a34fb2', - }, - createdAt: { - universalIdentifier: 'e7cfcf05-2676-4d43-9eee-4da1016b12ff', - }, - }, - }, + allBlocklists: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.blocklist, + fields: STANDARD_OBJECT_FIELDS.blocklist, + viewFieldNames: ['handle', 'workspaceMember', 'createdAt'], + }), blocklistRecordPageFields: { universalIdentifier: '5c679d04-7a1c-41be-9429-c9317ac7a0ea', viewFieldGroups: { @@ -221,24 +104,9 @@ export const STANDARD_OBJECTS = { }, }, calendarChannelEventAssociation: { - universalIdentifier: '20202020-491b-4aaa-9825-afd1bae6ae00', - fields: { - ...buildStandardObjectSystemFields( - '20202020-491b-4aaa-9825-afd1bae6ae00', - ), - calendarChannelId: { - universalIdentifier: '20202020-93ee-4da4-8d58-0282c4a9cb7d', - }, - calendarEvent: { - universalIdentifier: '20202020-5aa5-437e-bb86-f42d457783e3', - }, - eventExternalId: { - universalIdentifier: '20202020-9ec8-48bb-b279-21d0734a75a1', - }, - recurringEventExternalId: { - universalIdentifier: '20202020-c58f-4c69-9bf8-9518fa31aa50', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarChannelEventAssociation, + fields: STANDARD_OBJECT_FIELDS.calendarChannelEventAssociation, indexes: { calendarChannelIdIndex: { universalIdentifier: 'ff6b86c1-3112-4dfa-b734-c4789111a716', @@ -248,23 +116,17 @@ export const STANDARD_OBJECTS = { }, }, views: { - allCalendarChannelEventAssociations: { - universalIdentifier: '001893be-c06c-4ba1-9f18-53bd26f0179f', - viewFields: { - calendarChannelId: { - universalIdentifier: 'e3adffd2-d820-4c89-912c-34908d90057e', - }, - calendarEvent: { - universalIdentifier: '35656a84-ecb8-4075-a610-8b538d6f8120', - }, - eventExternalId: { - universalIdentifier: 'f779d7e8-f1d8-44a7-b0ef-4409c9b6b466', - }, - createdAt: { - universalIdentifier: '8ca74f2f-210b-4afc-81f0-506047400e82', - }, - }, - }, + allCalendarChannelEventAssociations: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarChannelEventAssociation, + fields: STANDARD_OBJECT_FIELDS.calendarChannelEventAssociation, + viewFieldNames: [ + 'calendarChannelId', + 'calendarEvent', + 'eventExternalId', + 'createdAt', + ], + }), calendarChannelEventAssociationRecordPageFields: { universalIdentifier: '766f254a-a0eb-45c8-b4d2-12311201e08f', viewFieldGroups: { @@ -296,33 +158,9 @@ export const STANDARD_OBJECTS = { }, }, calendarEventParticipant: { - universalIdentifier: '20202020-a1c3-47a6-9732-27e5b1e8436d', - fields: { - ...buildStandardObjectSystemFields( - '20202020-a1c3-47a6-9732-27e5b1e8436d', - ), - calendarEvent: { - universalIdentifier: '20202020-fe3a-401c-b889-af4f4657a861', - }, - handle: { - universalIdentifier: '20202020-8692-4580-8210-9e09cbd031a7', - }, - displayName: { - universalIdentifier: '20202020-ee1e-4f9f-8ac1-5c0b2f69691e', - }, - isOrganizer: { - universalIdentifier: '20202020-66e7-4e00-9e06-d06c92650580', - }, - responseStatus: { - universalIdentifier: '20202020-cec0-4be8-8fba-c366abc23147', - }, - person: { - universalIdentifier: '20202020-5761-4842-8186-e1898ef93966', - }, - workspaceMember: { - universalIdentifier: '20202020-20e4-4591-93ed-aeb17a4dcbd2', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEventParticipant, + fields: STANDARD_OBJECT_FIELDS.calendarEventParticipant, indexes: { calendarEventIdIndex: { universalIdentifier: 'c458ad97-8b95-43de-9003-88eb68576049', @@ -335,35 +173,21 @@ export const STANDARD_OBJECTS = { }, }, views: { - allCalendarEventParticipants: { - universalIdentifier: '5228d634-6b69-4a43-be5c-e778fa6fe779', - viewFields: { - calendarEvent: { - universalIdentifier: 'd9c2f346-b83b-48ae-98d0-e344f97248cd', - }, - handle: { - universalIdentifier: '4140bd68-55e8-475c-8724-7f9f97634a9f', - }, - displayName: { - universalIdentifier: '3cadc470-9231-4027-9bbe-60e934edb483', - }, - isOrganizer: { - universalIdentifier: '684972f9-c5fe-4fff-bdec-2fc5511c938c', - }, - responseStatus: { - universalIdentifier: 'dd0ab0bd-7f33-48fa-9461-fb5d085a2f9f', - }, - person: { - universalIdentifier: '86546244-9e3d-40e4-87cd-cbc82a353d2e', - }, - workspaceMember: { - universalIdentifier: '542141b0-ac85-4c43-867b-8d7f559b07ae', - }, - createdAt: { - universalIdentifier: '63d9d40d-e40c-410c-a14c-2f36c64c3e69', - }, - }, - }, + allCalendarEventParticipants: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEventParticipant, + fields: STANDARD_OBJECT_FIELDS.calendarEventParticipant, + viewFieldNames: [ + 'calendarEvent', + 'handle', + 'displayName', + 'isOrganizer', + 'responseStatus', + 'person', + 'workspaceMember', + 'createdAt', + ], + }), calendarEventParticipantRecordPageFields: { universalIdentifier: 'e01ebdb3-8fb8-46d2-8230-82242d593f7a', viewFieldGroups: { @@ -407,84 +231,25 @@ export const STANDARD_OBJECTS = { }, }, calendarEvent: { - universalIdentifier: '20202020-8f1d-4eef-9f85-0d1965e27221', - fields: { - ...buildStandardObjectSystemFields( - '20202020-8f1d-4eef-9f85-0d1965e27221', - ), - title: { universalIdentifier: '20202020-080e-49d1-b21d-9702a7e2525c' }, - isCanceled: { - universalIdentifier: '20202020-335b-4e04-b470-43b84b64863c', - }, - isFullDay: { - universalIdentifier: '20202020-551c-402c-bb6d-dfe9efe86bcb', - }, - startsAt: { - universalIdentifier: '20202020-2c57-4c75-93c5-2ac950a6ed67', - }, - endsAt: { universalIdentifier: '20202020-2554-4ee1-a617-17907f6bab21' }, - externalCreatedAt: { - universalIdentifier: '20202020-9f03-4058-a898-346c62181599', - }, - externalUpdatedAt: { - universalIdentifier: '20202020-b355-4c18-8825-ef42c8a5a755', - }, - description: { - universalIdentifier: '20202020-52c4-4266-a98f-e90af0b4d271', - }, - location: { - universalIdentifier: '20202020-641a-4ffe-960d-c3c186d95b17', - }, - iCalUid: { - universalIdentifier: '20202020-f24b-45f4-b6a3-d2f9fcb98714', - }, - conferenceSolution: { - universalIdentifier: '20202020-1c3f-4b5a-b526-5411a82179eb', - }, - conferenceLink: { - universalIdentifier: '20202020-35da-43ef-9ca0-e936e9dc237b', - }, - calendarChannelEventAssociations: { - universalIdentifier: '20202020-bdf8-4572-a2cc-ecbb6bcc3a02', - }, - calendarEventParticipants: { - universalIdentifier: '20202020-e07e-4ccb-88f5-6f3d00458eec', - }, - callRecordings: { - universalIdentifier: '48d6d151-18e2-4111-b405-d85fb9d860d8', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent, + fields: STANDARD_OBJECT_FIELDS.calendarEvent, indexes: {}, views: { - allCalendarEvents: { - universalIdentifier: '20202020-c001-4c01-8c01-ca1ebe0ca001', - viewFields: { - title: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf01', - }, - startsAt: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf02', - }, - endsAt: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf03', - }, - isFullDay: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf04', - }, - location: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf05', - }, - conferenceLink: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf06', - }, - isCanceled: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf07', - }, - createdAt: { - universalIdentifier: '20202020-cf01-4c01-8c01-ca1ebe0caf08', - }, - }, - }, + allCalendarEvents: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.calendarEvent, + fields: STANDARD_OBJECT_FIELDS.calendarEvent, + viewFieldNames: [ + 'title', + 'startsAt', + 'endsAt', + 'isFullDay', + 'location', + 'conferenceLink', + 'isCanceled', + 'createdAt', + ], + }), calendarEventRecordPageFields: { universalIdentifier: 'c73668d1-022d-4eaf-b825-4e2548180db6', viewFieldGroups: { @@ -537,74 +302,25 @@ export const STANDARD_OBJECTS = { }, }, callRecording: { - universalIdentifier: 'ce19efb9-710f-45b2-b141-473abbeea60b', - fields: { - ...buildStandardObjectSystemFields( - 'ce19efb9-710f-45b2-b141-473abbeea60b', - ), - title: { - universalIdentifier: '4cff8863-a1d1-45fd-a370-4eb6aa1f2a5b', - }, - status: { - universalIdentifier: '3e617680-d93e-4309-a54f-90f69528bfd7', - }, - recordingRequestStatus: { - universalIdentifier: '7fd681c9-244c-4e98-8939-7b175d472638', - }, - applicationId: { - universalIdentifier: '24ec1239-1240-42cb-8a2d-302632378e09', - }, - externalBotId: { - universalIdentifier: '0a2da128-9bcc-488b-bc31-65318c41bdf9', - }, - externalRecordingId: { - universalIdentifier: '6d17fb71-324b-4625-a5be-b3580607e2c7', - }, - startedAt: { - universalIdentifier: '6c56c23f-1987-410a-860a-df3b2b3f9a33', - }, - endedAt: { - universalIdentifier: '7a38a9cf-8424-4d6e-b80a-6883d3c662ef', - }, - video: { - universalIdentifier: 'bb9523d3-457e-4f4b-8c79-27a77afb87da', - }, - audio: { - universalIdentifier: '2eafc2d0-8fec-430c-a939-65ca5fbc0f08', - }, - transcript: { - universalIdentifier: '27b86d68-57d1-4607-aca0-191896b1ad43', - }, - summary: { - universalIdentifier: 'adb0f472-756b-4d3f-b21e-ea32bf73a5e4', - }, - calendarEvent: { - universalIdentifier: '49e64b28-bd98-4775-80ea-4781bdd45e35', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.callRecording, + fields: STANDARD_OBJECT_FIELDS.callRecording, indexes: { calendarEventIdIndex: { universalIdentifier: '8be3cc47-9352-4a1b-ad19-bb186bc0865d', }, }, views: { - allCallRecordings: { - universalIdentifier: 'c395b55e-88f0-4d5b-a1fb-0d38b50e0b19', - viewFields: { - status: { - universalIdentifier: '6c4a81a2-d9c1-4f82-984c-f97e083ca710', - }, - recordingRequestStatus: { - universalIdentifier: '3bdedacd-0fd5-4175-8d28-2fe41bb5aa77', - }, - title: { - universalIdentifier: 'b1d5051b-071d-4514-93cf-704724cdc8f6', - }, - startedAt: { - universalIdentifier: '3b96351f-66ed-4fa6-acb6-698647573af7', - }, - }, - }, + allCallRecordings: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.callRecording, + fields: STANDARD_OBJECT_FIELDS.callRecording, + viewFieldNames: [ + 'status', + 'recordingRequestStatus', + 'title', + 'startedAt', + ], + }), callRecordingRecordPageFields: { universalIdentifier: '99fa8b47-3b11-4f9b-8fbc-e67a9e1da682', viewFieldGroups: { @@ -646,69 +362,7 @@ export const STANDARD_OBJECTS = { }, company: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - ), - name: { universalIdentifier: '20202020-4d99-4e2e-a84c-4a27837b1ece' }, - domainName: { - universalIdentifier: '20202020-0c28-43d8-8ba5-3659924d3489', - }, - address: { universalIdentifier: '20202020-c5ce-4adc-b7b6-9c0979fc55e7' }, - linkedinLink: { - universalIdentifier: '20202020-ebeb-4beb-b9ad-6848036fb451', - }, - annualRevenue: { - universalIdentifier: '60f533b7-2166-4071-a767-ceb0286822fd', - }, - people: { universalIdentifier: '20202020-3213-4ddf-9494-6422bcff8d7c' }, - accountOwner: { - universalIdentifier: '20202020-95b8-4e10-9881-edb5d4765f9d', - }, - taskTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - }), - }, - noteTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - }), - }, - opportunities: { - universalIdentifier: '20202020-add3-4658-8e23-d70dccb6d0ec', - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.company, indexes: { accountOwnerIdIndex: { universalIdentifier: 'ec2ebfc9-0c9b-4597-a87d-aa295e2d8bfe', @@ -721,32 +375,20 @@ export const STANDARD_OBJECTS = { }, }, views: { - allCompanies: { - universalIdentifier: '20202020-a001-4a01-8a01-c0aba11c0001', - viewFields: { - name: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf001', - }, - domainName: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf002', - }, - createdBy: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf003', - }, - accountOwner: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf004', - }, - createdAt: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf005', - }, - linkedinLink: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf007', - }, - address: { - universalIdentifier: '20202020-af01-4a01-8a01-c0aba11cf008', - }, - }, - }, + allCompanies: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, + fields: STANDARD_OBJECT_FIELDS.company, + viewFieldNames: [ + 'name', + 'domainName', + 'createdBy', + 'accountOwner', + 'createdAt', + 'linkedinLink', + 'address', + ], + }), companyRecordPageFields: { universalIdentifier: '20202020-a001-4a01-8a01-c0aba11c1001', viewFieldGroups: { @@ -815,107 +457,24 @@ export const STANDARD_OBJECTS = { }, dashboard: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - ), - title: { universalIdentifier: '20202020-20ee-4091-95dc-44b57eda3a89' }, - pageLayoutId: { - universalIdentifier: '20202020-bb53-4648-aa36-1d9d54e6f7f2', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.dashboard, indexes: { searchVectorGinIndex: { universalIdentifier: 'e69f71aa-de0f-4b70-845f-7a8369c47928', }, }, views: { - allDashboards: { - universalIdentifier: '20202020-a012-4a12-8a12-da5ab0b0a001', - viewFields: { - title: { - universalIdentifier: '20202020-af12-4a12-8a12-da5ab0b0af01', - }, - createdBy: { - universalIdentifier: '20202020-af12-4a12-8a12-da5ab0b0af02', - }, - createdAt: { - universalIdentifier: '20202020-af12-4a12-8a12-da5ab0b0af03', - }, - updatedAt: { - universalIdentifier: '20202020-af12-4a12-8a12-da5ab0b0af04', - }, - }, - }, + allDashboards: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, + fields: STANDARD_OBJECT_FIELDS.dashboard, + viewFieldNames: ['title', 'createdBy', 'createdAt', 'updatedAt'], + }), }, }, messageCampaign: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, - ), - name: { universalIdentifier: 'dc8bca0e-37c4-4b78-814d-2e91124274e3' }, - subject: { universalIdentifier: '7251544c-b07a-4f0d-9d0a-48514367f230' }, - bodyTemplate: { - universalIdentifier: 'b3a69d08-31ca-4a8d-8359-5ca462899342', - }, - fromAddress: { - universalIdentifier: '91e1a33c-c1ff-411a-b720-9085e13c05db', - }, - status: { universalIdentifier: 'c7117256-3de6-48e1-87df-c99c32bad610' }, - sentAt: { universalIdentifier: 'e2315b4f-9edf-4df2-96b9-961e76368671' }, - sentCount: { - universalIdentifier: '2f333d2b-37b8-4ddc-ad0d-c07c6ce066ad', - }, - failedCount: { - universalIdentifier: 'd373fcd7-b1ce-4c77-8031-a5785c475028', - }, - bouncedCount: { - universalIdentifier: '20d884a9-34dd-4667-8ecb-ceec224258e2', - }, - complainedCount: { - universalIdentifier: '82842cfa-f12a-4bab-bbde-b2cf587d0406', - }, - unsubscribeTopicId: { - universalIdentifier: '0648e7ad-1769-4ff6-a4d5-72da79ef169c', - }, - list: { universalIdentifier: 'cb24dcdf-f0e8-4c71-8cff-70b714e86530' }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - messages: { universalIdentifier: 'e5a177a7-512b-4778-928e-69777a528f7c' }, - recipients: { - universalIdentifier: '05a3271c-5b91-493c-8f30-2d27b31d019e', - }, - }, + fields: STANDARD_OBJECT_FIELDS.messageCampaign, indexes: { unsubscribeTopicIdIndex: { universalIdentifier: 'efe8c20e-d12b-4475-969e-e86e0bbfe444', @@ -928,47 +487,25 @@ export const STANDARD_OBJECTS = { }, }, views: { - allMessageCampaigns: { - universalIdentifier: 'ffedb368-33f0-43a7-b84e-e622b4e97be9', - viewFields: { - name: { - universalIdentifier: '4d40646c-853d-48e6-8ddb-81e6dedabc99', - }, - subject: { - universalIdentifier: 'b017c851-e38d-4a81-ab38-0fb10e9f239e', - }, - status: { - universalIdentifier: 'ef35df97-b1a5-4f16-8b85-5751e5019f63', - }, - list: { - universalIdentifier: 'c0bf6a48-8695-4520-81e6-7462d35033b5', - }, - fromAddress: { - universalIdentifier: '553a0ea6-1f55-4143-a2d0-8c1ad0cf3dd1', - }, - sentAt: { - universalIdentifier: '7f4a9d81-afac-4d27-98de-0a2da65114f7', - }, - sentCount: { - universalIdentifier: '0be62e57-3955-49bd-a9ef-8f82f7a6e9aa', - }, - failedCount: { - universalIdentifier: 'ad346c8e-f462-499e-89c8-da01d5460dc6', - }, - bouncedCount: { - universalIdentifier: '135d3fb9-bc45-4426-980b-9836ed792e3a', - }, - complainedCount: { - universalIdentifier: '9957dc6d-05ae-4829-ae7d-7cdf9239cdd0', - }, - recipients: { - universalIdentifier: '2f99f444-4af3-44fd-b08d-46955c4ac2a2', - }, - createdAt: { - universalIdentifier: '6dffa47d-3128-4d54-924a-4f67676116c2', - }, - }, - }, + allMessageCampaigns: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, + fields: STANDARD_OBJECT_FIELDS.messageCampaign, + viewFieldNames: [ + 'name', + 'subject', + 'status', + 'list', + 'fromAddress', + 'sentAt', + 'sentCount', + 'failedCount', + 'bouncedCount', + 'complainedCount', + 'recipients', + 'createdAt', + ], + }), messageCampaignRecordPageFields: { universalIdentifier: '20202020-a009-4a09-8a09-fa9de11ca901', viewFieldGroups: { @@ -1001,64 +538,25 @@ export const STANDARD_OBJECTS = { }, messageList: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, - ), - name: { universalIdentifier: '69b9ed8b-7b26-4108-894f-05700ef7e8ee' }, - members: { - universalIdentifier: '92df3493-91cf-4665-8587-1b08917d299b', - }, - campaigns: { - universalIdentifier: 'e098d838-31ab-4812-91a8-f055f45a6832', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.messageList, indexes: { searchVectorGinIndex: { universalIdentifier: '8e205171-ed74-4620-b7d2-674aab85033a', }, }, views: { - allMessageLists: { - universalIdentifier: 'c72bae18-75e9-4cb0-baeb-379d3529b98f', - viewFields: { - name: { - universalIdentifier: 'c9767580-34e6-420b-923d-b4abd8c13d96', - }, - members: { - universalIdentifier: '6d314c4b-215a-4094-963a-ff9dd8221aea', - }, - campaigns: { - universalIdentifier: '037030a2-9dad-4bfd-957a-313b362172b4', - }, - createdAt: { - universalIdentifier: '29f3e7de-c40b-4597-8568-7318c146e4da', - }, - }, - }, + allMessageLists: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, + fields: STANDARD_OBJECT_FIELDS.messageList, + viewFieldNames: ['name', 'members', 'campaigns', 'createdAt'], + }), }, }, messageListMember: { - universalIdentifier: '27773d24-8ce3-40f8-aa6c-1f590f2c08d2', - fields: { - ...buildStandardObjectSystemFields( - '27773d24-8ce3-40f8-aa6c-1f590f2c08d2', - ), - person: { universalIdentifier: '34288425-8805-42fb-8b98-ee13d09be3d3' }, - list: { - universalIdentifier: 'd5402005-e8f9-4fbe-8696-b6723cd85018', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageListMember, + fields: STANDARD_OBJECT_FIELDS.messageListMember, indexes: { listIdIndex: { universalIdentifier: '61188470-6dcb-4b2a-b1a9-baeb688bccae', @@ -1069,33 +567,9 @@ export const STANDARD_OBJECTS = { }, }, messageChannelMessageAssociation: { - universalIdentifier: '20202020-ad1e-4127-bccb-d83ae04d2ccb', - fields: { - ...buildStandardObjectSystemFields( - '20202020-ad1e-4127-bccb-d83ae04d2ccb', - ), - messageChannelId: { - universalIdentifier: '20202020-b658-408f-bd46-3bd2d15d7e52', - }, - message: { - universalIdentifier: '20202020-da5d-4ac5-8743-342ab0a0336b', - }, - messageExternalId: { - universalIdentifier: '20202020-37d6-438f-b6fd-6503596c8f34', - }, - messageThread: { - universalIdentifier: '20202020-fac8-42a8-94dd-44dbc920ae16', - }, - messageThreadExternalId: { - universalIdentifier: '20202020-35fb-421e-afa0-0b8e8f7f9018', - }, - direction: { - universalIdentifier: '75c9b0f7-9e76-44d4-a2f9-47051e61eec7', - }, - messageFolders: { - universalIdentifier: '9bfc9da7-ae2d-44fd-9563-ede90c5d6222', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociation, + fields: STANDARD_OBJECT_FIELDS.messageChannelMessageAssociation, indexes: { messageChannelIdIndex: { universalIdentifier: '9894f9a3-0225-4e7b-9f6a-23d4e2576784', @@ -1108,26 +582,18 @@ export const STANDARD_OBJECTS = { }, }, views: { - allMessageChannelMessageAssociations: { - universalIdentifier: 'a4f465ac-d5cb-4f24-93ac-7a24bafd398e', - viewFields: { - messageChannelId: { - universalIdentifier: 'b86e652b-04ce-4089-9f71-e190eaf5b798', - }, - message: { - universalIdentifier: 'f9f2de0d-3db5-402b-a733-53be6a4667c8', - }, - messageExternalId: { - universalIdentifier: '7fb9801d-ca3d-4b2d-8d55-c922fcf7fefd', - }, - direction: { - universalIdentifier: 'ca38195e-985c-4880-85e0-26fa143c1ec7', - }, - createdAt: { - universalIdentifier: 'af239abd-2c55-4108-a9d8-b5a67f6ca2e2', - }, - }, - }, + allMessageChannelMessageAssociations: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociation, + fields: STANDARD_OBJECT_FIELDS.messageChannelMessageAssociation, + viewFieldNames: [ + 'messageChannelId', + 'message', + 'messageExternalId', + 'direction', + 'createdAt', + ], + }), messageChannelMessageAssociationRecordPageFields: { universalIdentifier: '680b43e2-5d50-49d8-bbdd-2d208e7b7071', viewFieldGroups: { @@ -1162,18 +628,10 @@ export const STANDARD_OBJECTS = { }, }, messageChannelMessageAssociationMessageFolder: { - universalIdentifier: '20202020-a1b0-40b0-8ab0-5b6c7d8e9f0a', - fields: { - ...buildStandardObjectSystemFields( - '20202020-a1b0-40b0-8ab0-5b6c7d8e9f0a', - ), - messageChannelMessageAssociation: { - universalIdentifier: '7411cfa3-4fd9-4b90-a636-940015fd7243', - }, - messageFolderId: { - universalIdentifier: 'b3369d31-3856-4a7a-b007-ee353918127c', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociationMessageFolder, + fields: + STANDARD_OBJECT_FIELDS.messageChannelMessageAssociationMessageFolder, indexes: { messageChannelMessageAssociationIdIndex: { universalIdentifier: '8e6038aa-1f79-4a84-87b5-f33caa172e98', @@ -1186,20 +644,18 @@ export const STANDARD_OBJECTS = { }, }, views: { - allMessageChannelMessageAssociationMessageFolders: { - universalIdentifier: '775610fe-f1d1-4959-bdc3-0b437059cfeb', - viewFields: { - messageChannelMessageAssociation: { - universalIdentifier: '1251e67a-e795-4bc2-a468-6cfc838b6a0a', - }, - messageFolderId: { - universalIdentifier: 'aff2203d-6439-43b8-9cb4-55e8d78bba43', - }, - createdAt: { - universalIdentifier: '9da7637e-25c7-4101-8169-b5f6ff159690', - }, - }, - }, + allMessageChannelMessageAssociationMessageFolders: + buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageChannelMessageAssociationMessageFolder, + fields: + STANDARD_OBJECT_FIELDS.messageChannelMessageAssociationMessageFolder, + viewFieldNames: [ + 'messageChannelMessageAssociation', + 'messageFolderId', + 'createdAt', + ], + }), messageChannelMessageAssociationMessageFolderRecordPageFields: { universalIdentifier: '331ec548-07d2-4f9d-a0a2-ef91a9f96184', viewFieldGroups: { @@ -1228,33 +684,9 @@ export const STANDARD_OBJECTS = { }, }, messageParticipant: { - universalIdentifier: '20202020-a433-4456-aa2d-fd9cb26b774a', - fields: { - ...buildStandardObjectSystemFields( - '20202020-a433-4456-aa2d-fd9cb26b774a', - ), - message: { - universalIdentifier: '20202020-985b-429a-9db9-9e55f4898a2a', - }, - role: { - universalIdentifier: '20202020-65d1-42f4-8729-c9ec1f52aecd', - }, - handle: { - universalIdentifier: '20202020-2456-464e-b422-b965a4db4a0b', - }, - displayName: { - universalIdentifier: '20202020-36dd-4a4f-ac02-228425be9fac', - }, - person: { - universalIdentifier: '20202020-249d-4e0f-82cd-1b9df5cd3da2', - }, - workspaceMember: { - universalIdentifier: '20202020-77a7-4845-99ed-1bcbb478be6f', - }, - messageCampaign: { - universalIdentifier: '5bc768db-919f-41da-8c43-df08084d526f', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageParticipant, + fields: STANDARD_OBJECT_FIELDS.messageParticipant, indexes: { messageIdIndex: { universalIdentifier: 'ab0863ba-f95e-493c-b86c-56e1bc7e5bc2', @@ -1270,32 +702,20 @@ export const STANDARD_OBJECTS = { }, }, views: { - allMessageParticipants: { - universalIdentifier: '8b7fbe7d-dae0-4285-8bdc-ec078a4de870', - viewFields: { - message: { - universalIdentifier: 'ca491a31-8659-4202-9476-f0f72efc80b5', - }, - role: { - universalIdentifier: '55b74f7e-7c58-4fce-a44b-a8d9671ec541', - }, - handle: { - universalIdentifier: 'abcbb5d9-b8c2-46bb-b3cc-ea035be8f3be', - }, - displayName: { - universalIdentifier: '8d0c8202-b57f-4450-a090-a7eb26aa2299', - }, - person: { - universalIdentifier: '26d0f3f1-43d3-425c-930c-81147451d0f8', - }, - workspaceMember: { - universalIdentifier: 'df62dcbc-c22d-4d34-9fa5-6f70bae02161', - }, - createdAt: { - universalIdentifier: '636ff7b6-86b8-49fc-9442-39f4c24ff424', - }, - }, - }, + allMessageParticipants: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageParticipant, + fields: STANDARD_OBJECT_FIELDS.messageParticipant, + viewFieldNames: [ + 'message', + 'role', + 'handle', + 'displayName', + 'person', + 'workspaceMember', + 'createdAt', + ], + }), messageParticipantRecordPageFields: { universalIdentifier: '209ab5c5-4a68-4d32-8255-515919a6c5f5', viewFieldGroups: { @@ -1333,75 +753,21 @@ export const STANDARD_OBJECTS = { }, }, messageThread: { - universalIdentifier: '20202020-849a-4c3e-84f5-a25a7d802271', - fields: { - ...buildStandardObjectSystemFields( - '20202020-849a-4c3e-84f5-a25a7d802271', - ), - messages: { - universalIdentifier: '20202020-3115-404f-aade-e1154b28e35a', - }, - messageChannelMessageAssociations: { - universalIdentifier: '20202020-314e-40a4-906d-a5d5d6c285f6', - }, - subject: { - universalIdentifier: 'a8ddbf8c-1137-45d1-b89e-5ffbd83f67c8', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageThread, + fields: STANDARD_OBJECT_FIELDS.messageThread, indexes: {}, views: { - allMessageThreads: { - universalIdentifier: '20202020-d002-4d02-8d02-ae55a9ba2002', - viewFields: { - subject: { - universalIdentifier: 'e5f0d32b-2b6a-47bc-b3bd-f32c96594ec1', - }, - messages: { - universalIdentifier: '20202020-df02-4d02-8d02-ae55a9ba2f01', - }, - updatedAt: { - universalIdentifier: 'af2c6ac9-7083-4609-8172-d518441f5e9e', - }, - createdAt: { - universalIdentifier: '20202020-df02-4d02-8d02-ae55a9ba2f02', - }, - }, - }, + allMessageThreads: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageThread, + fields: STANDARD_OBJECT_FIELDS.messageThread, + viewFieldNames: ['subject', 'messages', 'updatedAt', 'createdAt'], + }), }, }, message: { - universalIdentifier: '20202020-3f6b-4425-80ab-e468899ab4b2', - fields: { - ...buildStandardObjectSystemFields( - '20202020-3f6b-4425-80ab-e468899ab4b2', - ), - headerMessageId: { - universalIdentifier: '20202020-72b5-416d-aed8-b55609067d01', - }, - messageThread: { - universalIdentifier: '20202020-30f2-4ccd-9f5c-e41bb9d26214', - }, - subject: { universalIdentifier: '20202020-52d1-4036-b9ae-84bd722bb37a' }, - text: { universalIdentifier: '20202020-d2ee-4e7e-89de-9a0a9044a143' }, - receivedAt: { - universalIdentifier: '20202020-140a-4a2a-9f86-f13b6a979afc', - }, - messageParticipants: { - universalIdentifier: '20202020-7cff-4a74-b63c-73228448cbd9', - }, - messageChannelMessageAssociations: { - universalIdentifier: '20202020-3cef-43a3-82c6-50e7cfbc9ae4', - }, - messageCampaign: { - universalIdentifier: '77cff00b-a0ba-48d6-80de-0d5ccf14e45b', - }, - deliveryStatus: { - universalIdentifier: '209254fa-2b89-429d-a72a-c401c4bd5a78', - }, - isDraft: { - universalIdentifier: '20202020-4d3a-4b6e-9c1f-2a5e7b9d0c34', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.message, + fields: STANDARD_OBJECT_FIELDS.message, indexes: { messageThreadIdIndex: { universalIdentifier: '7a05b45e-7aa6-4a7e-9bbc-299cbed53c96', @@ -1411,90 +777,42 @@ export const STANDARD_OBJECTS = { }, }, views: { - allMessages: { - universalIdentifier: '20202020-d001-4d01-8d01-ae55a9e5a001', - viewFields: { - subject: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af01', - }, - messageThread: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af02', - }, - messageParticipants: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af03', - }, - receivedAt: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af04', - }, - headerMessageId: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af05', - }, - text: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af06', - }, - createdAt: { - universalIdentifier: '20202020-df01-4d01-8d01-ae55a9e5af07', - }, - }, - }, + allMessages: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.message, + fields: STANDARD_OBJECT_FIELDS.message, + viewFieldNames: [ + 'subject', + 'messageThread', + 'messageParticipants', + 'receivedAt', + 'headerMessageId', + 'text', + 'createdAt', + ], + }), }, }, note: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - ), - title: { universalIdentifier: '20202020-faeb-4c76-8ba6-ccbb0b4a965f' }, - bodyV2: { universalIdentifier: '20202020-a7bb-4d94-be51-8f25181502c8' }, - noteTargets: { - universalIdentifier: '20202020-1f25-43fe-8b00-af212fdde823', - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.note, indexes: { searchVectorGinIndex: { universalIdentifier: '8183c8d2-9114-4b6e-8c5d-12a3b14a5a13', }, }, views: { - allNotes: { - universalIdentifier: '20202020-a005-4a05-8a05-a0be5a11a000', - viewFields: { - title: { - universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11af00', - }, - noteTargets: { - universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11af01', - }, - bodyV2: { - universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11af02', - }, - createdBy: { - universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11af03', - }, - createdAt: { - universalIdentifier: '20202020-af05-4a05-8a05-a0be5a11af04', - }, - }, - }, + allNotes: buildStandardObjectIndexView({ + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, + fields: STANDARD_OBJECT_FIELDS.note, + viewFieldNames: [ + 'title', + 'noteTargets', + 'bodyV2', + 'createdBy', + 'createdAt', + ], + }), noteRecordPageFields: { universalIdentifier: '20202020-a005-4a05-8a05-a0be5a115001', viewFieldGroups: { @@ -1536,42 +854,7 @@ export const STANDARD_OBJECTS = { }, noteTarget: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - ), - note: { universalIdentifier: '20202020-57f3-4f50-9599-fc0f671df003' }, - targetPerson: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - }), - }, - targetCompany: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - }), - }, - targetOpportunity: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.noteTarget, morphIds: { targetMorphId: { morphId: '20202020-f635-435d-ab8d-e1168b375c70' }, }, @@ -1590,86 +873,23 @@ export const STANDARD_OBJECTS = { }, }, views: { - allNoteTargets: { - universalIdentifier: 'd124d587-ef78-402b-9341-7673e6cea033', - viewFields: { - id: { - universalIdentifier: 'f2d912fe-7c6f-4a9c-b808-b7b5a18d2818', - }, - note: { - universalIdentifier: '9d4ac173-d32b-4a44-9dbd-8a47ab844f98', - }, - targetPerson: { - universalIdentifier: 'b6f67de5-c5cf-4235-b740-a6a007c8eae3', - }, - targetCompany: { - universalIdentifier: 'a9c7f370-4b22-4f29-8e3f-678e91a59576', - }, - targetOpportunity: { - universalIdentifier: '3efeb162-cd03-458b-9c7b-47032d045204', - }, - }, - }, + allNoteTargets: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, + fields: STANDARD_OBJECT_FIELDS.noteTarget, + viewFieldNames: [ + 'id', + 'note', + 'targetPerson', + 'targetCompany', + 'targetOpportunity', + ], + }), }, }, opportunity: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - ), - name: { universalIdentifier: '20202020-8609-4f65-a2d9-44009eb422b5' }, - amount: { universalIdentifier: '20202020-583e-4642-8533-db761d5fa82f' }, - closeDate: { - universalIdentifier: '20202020-527e-44d6-b1ac-c4158d307b97', - }, - stage: { universalIdentifier: '20202020-6f76-477d-8551-28cd65b2b4b9' }, - pointOfContact: { - universalIdentifier: '20202020-8dfb-42fc-92b6-01afb759ed16', - }, - company: { universalIdentifier: '20202020-cbac-457e-b565-adece5fc815f' }, - owner: { universalIdentifier: '20202020-be7e-4d1e-8e19-3d5c7c4b9f2a' }, - taskTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - }), - }, - noteTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - }), - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.opportunity, indexes: { pointOfContactIdIndex: { universalIdentifier: 'b8c2a673-a981-4357-a43d-313a358e4daa', @@ -1685,29 +905,19 @@ export const STANDARD_OBJECTS = { }, }, views: { - allOpportunities: { - universalIdentifier: '20202020-a003-4a03-8a03-0aa0b1ca1ba0', - viewFields: { - name: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1baf', - }, - amount: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1bb0', - }, - createdBy: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1bb1', - }, - closeDate: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1bb2', - }, - company: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1bb3', - }, - pointOfContact: { - universalIdentifier: '20202020-af03-4a03-8a03-0aa0b1ca1bb4', - }, - }, - }, + allOpportunities: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, + fields: STANDARD_OBJECT_FIELDS.opportunity, + viewFieldNames: [ + 'name', + 'amount', + 'createdBy', + 'closeDate', + 'company', + 'pointOfContact', + ], + }), byStage: { universalIdentifier: '20202020-a004-4a04-8a04-0aa0b1ca1ba0', viewFields: { @@ -1810,77 +1020,7 @@ export const STANDARD_OBJECTS = { }, person: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - ), - name: { universalIdentifier: '20202020-3875-44d5-8c33-a6239011cab8' }, - emails: { universalIdentifier: '20202020-3c51-43fa-8b6e-af39e29368ab' }, - linkedinLink: { - universalIdentifier: '20202020-f1af-48f7-893b-2007a73dd508', - }, - jobTitle: { universalIdentifier: '20202020-b0d0-415a-bef9-640a26dacd9b' }, - phones: { universalIdentifier: '20202020-0638-448e-8825-439134618022' }, - avatarUrl: { - universalIdentifier: '20202020-b8a6-40df-961c-373dc5d2ec21', - }, - avatarFile: { - universalIdentifier: '20202020-a7c9-4e3d-8f1b-2d5a6b7c8e9f', - }, - company: { universalIdentifier: '20202020-e2f3-448e-b34c-2d625f0025fd' }, - pointOfContactForOpportunities: { - universalIdentifier: '20202020-911b-4a7d-b67b-918aa9a5b33a', - }, - taskTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - }), - }, - noteTargets: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.noteTarget, - }), - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - messageParticipants: { - universalIdentifier: '20202020-498e-4c61-8158-fa04f0638334', - }, - calendarEventParticipants: { - universalIdentifier: '20202020-52ee-45e9-a702-b64b3753e3a9', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - listMemberships: { - universalIdentifier: '8b8d1be0-4c94-4413-a2c9-c7ede205a81d', - }, - }, + fields: STANDARD_OBJECT_FIELDS.person, indexes: { companyIdIndex: { universalIdentifier: '8a265a5c-d3ae-47dc-bdf9-b42cfa2ba639', @@ -1893,35 +1033,20 @@ export const STANDARD_OBJECTS = { }, }, views: { - allPeople: { - universalIdentifier: '20202020-a002-4a02-8a02-ae0a1ea11a00', - viewFields: { - name: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af0', - }, - emails: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af1', - }, - createdBy: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af2', - }, - company: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af3', - }, - phones: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af4', - }, - createdAt: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af5', - }, - jobTitle: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af7', - }, - linkedinLink: { - universalIdentifier: '20202020-af02-4a02-8a02-ae0a1ea11af8', - }, - }, - }, + allPeople: buildStandardObjectIndexView({ + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, + fields: STANDARD_OBJECT_FIELDS.person, + viewFieldNames: [ + 'name', + 'emails', + 'createdBy', + 'company', + 'phones', + 'createdAt', + 'jobTitle', + 'linkedinLink', + ], + }), personRecordPageFields: { universalIdentifier: '20202020-a002-4a02-8a02-ae0a1ea12001', viewFieldGroups: { @@ -1999,37 +1124,7 @@ export const STANDARD_OBJECTS = { }, task: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - ), - title: { universalIdentifier: '20202020-b386-4cb7-aa5a-08d4a4d92680' }, - bodyV2: { universalIdentifier: '20202020-4aa0-4ae8-898d-7df0afd47ab1' }, - dueAt: { universalIdentifier: '20202020-fd99-40da-951b-4cb9a352fce3' }, - status: { universalIdentifier: '20202020-70bc-48f9-89c5-6aa730b151e0' }, - taskTargets: { - universalIdentifier: '20202020-de9c-4d0e-a452-713d4a3e5fc7', - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - assignee: { universalIdentifier: '20202020-065a-4f42-a906-e20422c1753f' }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.task, indexes: { assigneeIdIndex: { universalIdentifier: 'f48fa3b1-0cec-44da-a9e5-f8a5e766637e', @@ -2039,35 +1134,20 @@ export const STANDARD_OBJECTS = { }, }, views: { - allTasks: { - universalIdentifier: '20202020-a006-4a06-8a06-ba5ca11a1ea0', - viewFields: { - title: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eaf', - }, - status: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb0', - }, - taskTargets: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb1', - }, - createdBy: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb2', - }, - dueAt: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb3', - }, - assignee: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb4', - }, - bodyV2: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb5', - }, - createdAt: { - universalIdentifier: '20202020-af06-4a06-8a06-ba5ca11a1eb6', - }, - }, - }, + allTasks: buildStandardObjectIndexView({ + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, + fields: STANDARD_OBJECT_FIELDS.task, + viewFieldNames: [ + 'title', + 'status', + 'taskTargets', + 'createdBy', + 'dueAt', + 'assignee', + 'bodyV2', + 'createdAt', + ], + }), assignedToMe: { universalIdentifier: '20202020-a007-4a07-8a07-ba5ca551aaed', viewFields: { @@ -2194,42 +1274,7 @@ export const STANDARD_OBJECTS = { }, taskTarget: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - ), - task: { universalIdentifier: '20202020-e881-457a-8758-74aaef4ae78a' }, - targetPerson: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - }), - }, - targetCompany: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - }), - }, - targetOpportunity: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.taskTarget, morphIds: { targetMorphId: { morphId: '20202020-f636-435d-ab8d-e1168b375c71' }, }, @@ -2248,164 +1293,23 @@ export const STANDARD_OBJECTS = { }, }, views: { - allTaskTargets: { - universalIdentifier: '1dbf1d24-6cca-4f55-ae2f-e3d1b425a495', - viewFields: { - id: { - universalIdentifier: 'a49287c9-8aa6-4fca-9ec5-08d643f7239f', - }, - task: { - universalIdentifier: '1f79839e-42f6-4a69-839a-369e21a7497d', - }, - targetPerson: { - universalIdentifier: 'cadc7a33-1527-4ef8-ac00-7ed0b54d1bae', - }, - targetCompany: { - universalIdentifier: 'e9fa1305-4ba2-41c5-9198-fdc622b69f90', - }, - targetOpportunity: { - universalIdentifier: '526f3354-34d6-4e7e-a870-5f99c28353c2', - }, - }, - }, + allTaskTargets: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.taskTarget, + fields: STANDARD_OBJECT_FIELDS.taskTarget, + viewFieldNames: [ + 'id', + 'task', + 'targetPerson', + 'targetCompany', + 'targetOpportunity', + ], + }), }, }, timelineActivity: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - ), - name: { universalIdentifier: '20202020-7207-46e8-9dab-849505ae8497' }, - happensAt: { - universalIdentifier: '20202020-9526-4993-b339-c4318c4d39f0', - }, - properties: { - universalIdentifier: '20202020-f142-4b04-b91b-6a2b4af3bf11', - }, - workspaceMember: { - universalIdentifier: '20202020-af23-4479-9a30-868edc474b36', - }, - targetPerson: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person, - }), - }, - targetCompany: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company, - }), - }, - targetOpportunity: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity, - }), - }, - targetTask: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task, - }), - }, - targetNote: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.note, - }), - }, - targetWorkflow: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - }), - }, - targetWorkflowVersion: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, - }), - }, - targetWorkflowRun: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, - }), - }, - targetDashboard: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.dashboard, - }), - }, - targetMessageList: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageList, - }), - }, - targetMessageCampaign: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.messageCampaign, - }), - }, - linkedRecordCachedName: { - universalIdentifier: '20202020-cfdb-4bef-bbce-a29f41230934', - }, - linkedRecordId: { - universalIdentifier: '20202020-2e0e-48c0-b445-ee6c1e61687d', - }, - linkedObjectMetadataId: { - universalIdentifier: '20202020-c595-449d-9f89-562758c9ee69', - }, - }, + fields: STANDARD_OBJECT_FIELDS.timelineActivity, morphIds: { targetMorphId: { morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1' }, }, @@ -2442,162 +1346,67 @@ export const STANDARD_OBJECTS = { }, }, views: { - allTimelineActivities: { - universalIdentifier: '20202020-b101-4b01-8b01-ba5cc01aa001', - viewFields: { - name: { - universalIdentifier: '20202020-bf01-4b01-8b01-ba5cc01aa011', - }, - happensAt: { - universalIdentifier: '20202020-bf01-4b01-8b01-ba5cc01aa012', - }, - properties: { - universalIdentifier: '20202020-bf01-4b01-8b01-ba5cc01aa019', - }, - workspaceMember: { - universalIdentifier: '20202020-bf01-4b01-8b01-ba5cc01aa013', - }, - linkedRecordCachedName: { - universalIdentifier: '20202020-bf01-4b01-8b01-ba5cc01aa017', - }, - targetPerson: { - universalIdentifier: '37b38a8b-abd7-4f72-92d2-ad82bbef0296', - }, - targetCompany: { - universalIdentifier: '2015825f-0786-4b0d-88a7-dfce1b4b1c1a', - }, - targetOpportunity: { - universalIdentifier: 'f7b5ced9-eba6-4454-8849-7a92d27c11ca', - }, - targetTask: { - universalIdentifier: '3899138d-e6fa-414c-9432-214c9b797ebb', - }, - targetNote: { - universalIdentifier: 'ab74ed52-0195-4b65-987a-8367c07ee222', - }, - targetWorkflow: { - universalIdentifier: 'd2c3ddc3-afad-40b9-a2cb-d2765f2f5691', - }, - targetWorkflowVersion: { - universalIdentifier: '4a7e3213-afd5-4691-8bba-0a10e8697afb', - }, - targetWorkflowRun: { - universalIdentifier: '97910946-04f0-4634-804e-880bc0019225', - }, - targetDashboard: { - universalIdentifier: '538847e8-ab09-407c-a433-505f6d7be7a1', - }, - }, - }, + allTimelineActivities: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, + fields: STANDARD_OBJECT_FIELDS.timelineActivity, + viewFieldNames: [ + 'name', + 'happensAt', + 'workspaceMember', + 'targetPerson', + 'targetCompany', + 'targetOpportunity', + 'targetTask', + 'targetNote', + 'targetWorkflow', + 'targetWorkflowVersion', + 'targetWorkflowRun', + 'targetDashboard', + ], + }), }, }, workflow: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - ), - name: { universalIdentifier: '20202020-b3d3-478f-acc0-5d901e725b20' }, - lastPublishedVersionId: { - universalIdentifier: '20202020-326a-4fba-8639-3456c0a169e8', - }, - coreWorkflowId: { - universalIdentifier: '20202020-058a-42ad-8eb8-0662a5552aad', - }, - statuses: { universalIdentifier: '20202020-357c-4432-8c50-8c31b4a552d9' }, - versions: { universalIdentifier: '20202020-9432-416e-8f3c-27ee3153d099' }, - runs: { universalIdentifier: '20202020-759b-4340-b58b-e73595c4df4f' }, - automatedTriggers: { - universalIdentifier: '20202020-3319-4234-a34c-117ecad2b8a9', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - attachments: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.attachment, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.workflow, indexes: { searchVectorGinIndex: { universalIdentifier: 'c7e64c55-eb0c-4b93-b076-5cfcf2e2e042', }, }, views: { - allWorkflows: { - universalIdentifier: '20202020-a009-4a09-8a09-a0bcf10aa11a', - viewFields: { - name: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11a', - }, - statuses: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11b', - }, - updatedAt: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11c', - }, - createdBy: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11d', - }, - versions: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11e', - }, - runs: { - universalIdentifier: '20202020-af09-4a09-8a09-a0bcf10aa11f', - }, - }, - }, + allWorkflows: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflow, + fields: STANDARD_OBJECT_FIELDS.workflow, + viewFieldNames: [ + 'name', + 'statuses', + 'updatedAt', + 'createdBy', + 'versions', + 'runs', + ], + }), }, }, workflowAutomatedTrigger: { - universalIdentifier: '20202020-3319-4234-a34c-7f3b9d2e4d1f', - fields: { - ...buildStandardObjectSystemFields( - '20202020-3319-4234-a34c-7f3b9d2e4d1f', - ), - type: { - universalIdentifier: '20202020-3319-4234-a34c-3f92c1ab56e7', - }, - settings: { - universalIdentifier: '20202020-3319-4234-a34c-bac8f903de12', - }, - workflow: { - universalIdentifier: '20202020-3319-4234-a34c-8e1a4d2f7c03', - }, - }, + universalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowAutomatedTrigger, + fields: STANDARD_OBJECT_FIELDS.workflowAutomatedTrigger, indexes: { workflowIdIndex: { universalIdentifier: '7331ff89-a3f9-4ac0-9fa9-0de5663ae7b2', }, }, views: { - allWorkflowAutomatedTriggers: { - universalIdentifier: 'a0a9ef79-3d42-417a-8555-3ee54c18ea51', - viewFields: { - type: { - universalIdentifier: '689b4749-aa40-489a-bf0b-475a197ca2e6', - }, - workflow: { - universalIdentifier: 'e5a46195-06fe-4f47-8844-128e35151d37', - }, - createdAt: { - universalIdentifier: 'bb35e66a-2a1e-416b-8105-5749d91ab65f', - }, - }, - }, + allWorkflowAutomatedTriggers: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowAutomatedTrigger, + fields: STANDARD_OBJECT_FIELDS.workflowAutomatedTrigger, + viewFieldNames: ['type', 'workflow', 'createdAt'], + }), workflowAutomatedTriggerRecordPageFields: { universalIdentifier: '10aff295-f7ac-475d-8528-661eb9aa9759', viewFieldGroups: { @@ -2627,40 +1436,7 @@ export const STANDARD_OBJECTS = { }, workflowRun: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, - ), - name: { universalIdentifier: '20202020-b840-4253-aef9-4e5013694587' }, - workflowVersion: { - universalIdentifier: '20202020-2f52-4ba8-8dc4-d0d6adb9578d', - }, - workflow: { - universalIdentifier: '20202020-8c57-4e7f-84f5-f373f68e1b82', - }, - enqueuedAt: { - universalIdentifier: '20202020-f1e3-4de1-a461-b5c4fdbc861d', - }, - startedAt: { - universalIdentifier: '20202020-a234-4e2d-bd15-85bcea6bb183', - }, - endedAt: { universalIdentifier: '20202020-e1c1-4b6b-bbbd-b2beaf2e159e' }, - status: { universalIdentifier: '20202020-6b3e-4f9c-8c2b-2e5b8e6d6f3b' }, - state: { universalIdentifier: '20202020-611f-45f3-9cde-d64927e8ec57' }, - stepLogs: { - universalIdentifier: '20202020-7c4e-4e1a-8fc1-1e3a55d6c2a1', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.workflowRun, indexes: { workflowVersionIdIndex: { universalIdentifier: '8183c8d2-9114-4b6e-8c5d-12a3b14a5a14', @@ -2673,29 +1449,12 @@ export const STANDARD_OBJECTS = { }, }, views: { - allWorkflowRuns: { - universalIdentifier: '20202020-a011-4a11-8a11-a0bcf10abca5', - viewFields: { - name: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcaf', - }, - workflow: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcb0', - }, - status: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcb1', - }, - startedAt: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcb2', - }, - createdBy: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcb3', - }, - workflowVersion: { - universalIdentifier: '20202020-af11-4a11-8a11-a0bcf10abcb4', - }, - }, - }, + allWorkflowRuns: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowRun, + fields: STANDARD_OBJECT_FIELDS.workflowRun, + viewFieldNames: ['name', 'workflow', 'status'], + }), workflowRunRecordPageFields: { universalIdentifier: '20202020-a011-4a11-8a11-a0bcf10abcf1', viewFields: { @@ -2749,36 +1508,7 @@ export const STANDARD_OBJECTS = { }, workflowVersion: { universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, - fields: { - ...buildStandardObjectSystemFields( - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, - ), - name: { universalIdentifier: '20202020-a12f-4cca-9937-a2e40cc65509' }, - workflow: { - universalIdentifier: '20202020-afa3-46c3-91b0-0631ca6aa1c8', - }, - trigger: { - universalIdentifier: '20202020-4eae-43e7-86e0-212b41a30b48', - }, - status: { - universalIdentifier: '20202020-5a34-440e-8a25-39d8c3d1d4cf', - }, - runs: { universalIdentifier: '20202020-1d08-46df-901a-85045f18099a' }, - steps: { universalIdentifier: '20202020-5988-4a64-b94a-1f9b7b989039' }, - coreWorkflowVersionId: { - universalIdentifier: '20202020-58b4-46e8-b6d2-f1f3c74cf7f4', - }, - timelineActivities: { - universalIdentifier: getSystemRelationFieldUniversalIdentifier({ - applicationUniversalIdentifier: - TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, - objectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, - relationTargetObjectUniversalIdentifier: - STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.timelineActivity, - }), - }, - }, + fields: STANDARD_OBJECT_FIELDS.workflowVersion, indexes: { workflowIdIndex: { universalIdentifier: '8138c3b3-0b14-4ee1-be0e-debdde6b3219', @@ -2788,26 +1518,12 @@ export const STANDARD_OBJECTS = { }, }, views: { - allWorkflowVersions: { - universalIdentifier: '20202020-a010-4a10-8a10-a0bcf10aae15', - viewFields: { - name: { - universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeaf', - }, - workflow: { - universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeb0', - }, - status: { - universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeb1', - }, - updatedAt: { - universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeb2', - }, - runs: { - universalIdentifier: '20202020-af10-4a10-8a10-a0bcf10aaeb3', - }, - }, - }, + allWorkflowVersions: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workflowVersion, + fields: STANDARD_OBJECT_FIELDS.workflowVersion, + viewFieldNames: ['name', 'workflow', 'status', 'updatedAt', 'runs'], + }), workflowVersionRecordPageFields: { universalIdentifier: '20202020-a010-4a10-8a10-a0bcf10aaef1', viewFields: { @@ -2854,67 +1570,8 @@ export const STANDARD_OBJECTS = { }, }, workspaceMember: { - universalIdentifier: '20202020-3319-4234-a34c-82d5c0e881a6', - fields: { - ...buildStandardObjectSystemFields( - '20202020-3319-4234-a34c-82d5c0e881a6', - ), - name: { universalIdentifier: '20202020-e914-43a6-9c26-3603c59065f4' }, - colorScheme: { - universalIdentifier: '20202020-66bc-47f2-adac-f2ef7c598b63', - }, - locale: { - universalIdentifier: '20202020-402e-4695-b169-794fa015afbe', - }, - avatarUrl: { - universalIdentifier: '20202020-0ced-4c4f-a376-c98a966af3f6', - }, - userEmail: { - universalIdentifier: '20202020-4c5f-4e09-bebc-9e624e21ecf4', - }, - jobTitle: { - universalIdentifier: '20202020-4dd4-4619-826e-08f6c06b374d', - }, - userId: { - universalIdentifier: '20202020-75a9-4dfc-bf25-2e4b43e89820', - }, - assignedTasks: { - universalIdentifier: '20202020-61dc-4a1c-99e8-38ebf8d2bbeb', - }, - ownedOpportunities: { - universalIdentifier: '20202020-9e4d-4b3a-8c1f-6d7e8f9a0b1c', - }, - accountOwnerForCompanies: { - universalIdentifier: '20202020-dc29-4bd4-a3c1-29eafa324bee', - }, - messageParticipants: { - universalIdentifier: '20202020-8f99-48bc-a5eb-edd33dd54188', - }, - blocklist: { - universalIdentifier: '20202020-6cb2-4161-9f29-a4b7f1283859', - }, - calendarEventParticipants: { - universalIdentifier: '20202020-0dbc-4841-9ce1-3e793b5b3512', - }, - timelineActivities: { - universalIdentifier: '20202020-e15b-47b8-94fe-8200e3c66615', - }, - timeZone: { - universalIdentifier: '20202020-2d33-4c21-a86e-5943b050dd54', - }, - dateFormat: { - universalIdentifier: '20202020-af13-4e11-b1e7-b8cf5ea13dc0', - }, - timeFormat: { - universalIdentifier: '20202020-8acb-4cf8-a851-a6ed443c8d81', - }, - calendarStartDay: { - universalIdentifier: '20202020-1ecc-4562-84c9-ff3a2f6cce85', - }, - numberFormat: { - universalIdentifier: '20202020-7f40-4e7f-b126-11c0eda6b141', - }, - }, + universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember, + fields: STANDARD_OBJECT_FIELDS.workspaceMember, indexes: { userEmailUniqueIndex: { universalIdentifier: '76da5f27-523c-44b6-ad06-12954f6b949f', @@ -2924,44 +1581,17 @@ export const STANDARD_OBJECTS = { }, }, views: { - allWorkspaceMembers: { - universalIdentifier: '20202020-e001-4e01-8e01-a0bcaeabe100', - viewFields: { - name: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f0', - }, - userEmail: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f1', - }, - avatarUrl: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f2', - }, - colorScheme: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f3', - }, - locale: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f4', - }, - timeZone: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f5', - }, - dateFormat: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f6', - }, - timeFormat: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f7', - }, - createdAt: { - universalIdentifier: '20202020-ef01-4e01-8e01-a0bcaeabe1f8', - }, - ownedOpportunities: { - universalIdentifier: '8a0503f3-ba61-453e-86dc-6c79f7bc235b', - }, - assignedTasks: { - universalIdentifier: 'af16226e-6375-4676-8bd9-9d1a57076fc4', - }, - }, - }, + allWorkspaceMembers: buildStandardObjectIndexView({ + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember, + fields: STANDARD_OBJECT_FIELDS.workspaceMember, + viewFieldNames: [ + 'name', + 'createdAt', + 'ownedOpportunities', + 'assignedTasks', + ], + }), }, }, } as const satisfies Record< diff --git a/packages/twenty-shared/src/metadata/index.ts b/packages/twenty-shared/src/metadata/index.ts index ee1cc4bc58..2f8ef1f74d 100644 --- a/packages/twenty-shared/src/metadata/index.ts +++ b/packages/twenty-shared/src/metadata/index.ts @@ -16,6 +16,7 @@ export { ALL_METADATA_NAME } from './constants/all-metadata-name.constant'; export { DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS } from './constants/default-relations-object-standard-ids.constant'; export { IDENTIFIER_MAX_CHAR_LENGTH } from './constants/identifier-max-char-length.constant'; export { RESERVED_METADATA_NAME_KEYWORDS } from './constants/reserved-metadata-name-keywords.constant'; +export { STANDARD_OBJECT_FIELDS } from './constants/standard-object-fields.constant'; export type { StandardObjectWithUniversalIdentifierName } from './constants/standard-object-universal-identifiers.constant'; export { STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from './constants/standard-object-universal-identifiers.constant'; export { STANDARD_OBJECTS } from './constants/standard-object.constant'; 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 new file mode 100644 index 0000000000..4fb6e3a1e0 --- /dev/null +++ b/packages/twenty-shared/src/metadata/utils/internal/build-standard-object-index-view.util.ts @@ -0,0 +1,57 @@ +import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from '@/application/constants/TwentyStandardApplicationUniversalIdentifier'; +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 }; + +export const buildStandardObjectIndexView = < + const TViewFieldName extends string, +>({ + objectUniversalIdentifier, + fields, + viewFieldNames, +}: { + objectUniversalIdentifier: string; + fields: Record; + viewFieldNames: readonly TViewFieldName[]; +}): { + universalIdentifier: string; + viewFields: Record; +} => { + const viewUniversalIdentifier = getSystemViewUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier, + viewKey: ViewKey.INDEX, + }); + + const viewFields = Object.fromEntries( + viewFieldNames.map((viewFieldName) => { + const field = fields[viewFieldName]; + + if (field === undefined) { + throw new Error( + `Missing field "${viewFieldName}" for the INDEX view of object ${objectUniversalIdentifier}`, + ); + } + + return [ + viewFieldName, + { + universalIdentifier: getViewFieldUniversalIdentifier({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER, + viewUniversalIdentifier, + fieldMetadataUniversalIdentifier: field.universalIdentifier, + }), + }, + ]; + }), + ) as Record; + + return { + universalIdentifier: viewUniversalIdentifier, + viewFields, + }; +};