Add default relation to standard object on custom object in manifest (#18033)
add default relation fields when creating an object from an application --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { type ExampleOptions } from '@/types/scaffolding-options';
|
||||
import { GENERATED_DIR } from 'twenty-shared/application';
|
||||
import { copyBaseApplicationProject } from '@/utils/app-template';
|
||||
import * as fs from 'fs-extra';
|
||||
import { tmpdir } from 'os';
|
||||
@@ -111,7 +112,7 @@ describe('copyBaseApplicationProject', () => {
|
||||
|
||||
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
|
||||
expect(gitignoreContent).toContain('/node_modules');
|
||||
expect(gitignoreContent).toContain('generated');
|
||||
expect(gitignoreContent).toContain(GENERATED_DIR);
|
||||
});
|
||||
|
||||
it('should create yarn.lock file', async () => {
|
||||
|
||||
+801
@@ -77,6 +77,107 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
],
|
||||
|
||||
fields: [
|
||||
// Reverse relation fields for rootNote (5 fields)
|
||||
{
|
||||
description: 'RootNote Root note',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
label: 'RootNote',
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'RootNote Root note',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
label: 'RootNote',
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'bbc0dc11-9daa-5e3f-b939-e6f0a12bb277',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '13fb0eae-50b4-5e28-afbc-5b5a9c422ead',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'RootNote Root note',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
label: 'RootNote',
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'aa05ee89-bd8a-5911-a4d5-3355926186bc',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'RootNote Root note',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'RootNote',
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'762ad37e-d624-51d8-989c-3a119eef9e7f',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'c327faff-76eb-5baf-8b35-1c31e57306fb',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'RootNote Root note',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'RootNote',
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '36843050-e6e3-5759-99b6-e975426bad73',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
// User-defined relation and field definitions
|
||||
{
|
||||
label: 'Post Card',
|
||||
name: 'postCard',
|
||||
@@ -176,6 +277,306 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
// Reverse relation fields for postCardRecipient (5 fields)
|
||||
{
|
||||
description: 'PostCardRecipient Post Card Recipient',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
label: 'PostCardRecipient',
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '18b6354d-8b0f-5be6-9473-d635aed670a1',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCardRecipient Post Card Recipient',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
label: 'PostCardRecipient',
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'a284914f-650c-54ad-84ef-d6229d3fa453',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'f3247843-dd1f-5b15-8873-c0062a021180',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCardRecipient Post Card Recipient',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
label: 'PostCardRecipient',
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'82497ec7-0121-53d6-9518-ae7434ceaf8b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'ba74dfd1-e7ee-502d-abad-82e14d595874',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCardRecipient Post Card Recipient',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'PostCardRecipient',
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'6de8b57e-ed0a-5d35-bd39-bd8874445810',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '9d5a9f4b-5431-5b90-810a-1e9d8873b302',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCardRecipient Post Card Recipient',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'PostCardRecipient',
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'00459e7e-8ef1-5a9b-b180-26bbb54a2696',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '85353e69-1e70-51ef-91c3-334d85013a4b',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
// Reverse relation fields for postCard (5 fields)
|
||||
{
|
||||
description: 'PostCard Post card',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
label: 'PostCard',
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '1bdc52a0-0339-5360-ba17-b615250ef730',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCard Post card',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
label: 'PostCard',
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'94f43ec4-d2d1-5a50-b414-78da27d24258',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '9a5d4ae3-6f45-593f-923d-bcd16ede4345',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCard Post card',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
label: 'PostCard',
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'547c93e4-c9e7-5880-b251-46230fb59bfa',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCard Post card',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'PostCard',
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'47deea5c-8559-5ae7-991a-60c9f7102f86',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'ca687361-ac2b-5722-9ad9-cd37427d087a',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'PostCard Post card',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'PostCard',
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
// Reverse relation fields for recipient (5 fields)
|
||||
{
|
||||
description: 'Recipient Recipient',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
label: 'Recipient',
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'b01f6937-4859-587b-b3df-92e0a6f65e93',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipient Recipient',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
label: 'Recipient',
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'd16a6387-273b-563c-a6cc-2cb4524b9b7b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'f4e6fd66-83bb-5f2d-b1ec-a3db2e0ca32d',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipient Recipient',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
label: 'Recipient',
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'244a7841-9e45-5ed1-a830-83e73c1f13fa',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '9c87e1b8-74a2-5833-9237-281d561d2a7d',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipient Recipient',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'Recipient',
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'eab71eb1-b457-555a-8a2b-f292cef1671d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '23508618-7ed8-519e-9fee-a57b94dc98e9',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipient Recipient',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
label: 'Recipient',
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'305f1890-04ba-5101-b18f-a1ca25a1d8a7',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '20500ff3-2410-5bfc-b2e7-804785c07770',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
},
|
||||
],
|
||||
objects: [
|
||||
{
|
||||
@@ -269,6 +670,106 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '38e2c671-cbf5-5773-a04a-e5a2d6c323c8',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '3b56cdea-cf85-51d4-a7a0-cd701f76141a',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '7c6d88f5-a348-51dd-8d0b-74a17e626754',
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Favorites',
|
||||
name: 'favorites',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'13fb0eae-50b4-5e28-afbc-5b5a9c422ead',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'bbc0dc11-9daa-5e3f-b939-e6f0a12bb277',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'aa05ee89-bd8a-5911-a4d5-3355926186bc',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'c327faff-76eb-5baf-8b35-1c31e57306fb',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '762ad37e-d624-51d8-989c-3a119eef9e7f',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'36843050-e6e3-5759-99b6-e975426bad73',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: 'IconNote',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
@@ -367,6 +868,106 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '1dd75ff5-5fd0-51df-b3a3-a12e3aff30df',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '6c5e91f5-e323-555b-84e0-5a1dd7f36b1b',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '9fa18b19-41b6-5082-b3e2-4e73f7461164',
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'18b6354d-8b0f-5be6-9473-d635aed670a1',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Favorites',
|
||||
name: 'favorites',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'f3247843-dd1f-5b15-8873-c0062a021180',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'a284914f-650c-54ad-84ef-d6229d3fa453',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ba74dfd1-e7ee-502d-abad-82e14d595874',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '82497ec7-0121-53d6-9518-ae7434ceaf8b',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9d5a9f4b-5431-5b90-810a-1e9d8873b302',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '6de8b57e-ed0a-5d35-bd39-bd8874445810',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'85353e69-1e70-51ef-91c3-334d85013a4b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '00459e7e-8ef1-5a9b-b180-26bbb54a2696',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: 'IconLink',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
@@ -525,6 +1126,106 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'dc5231a4-e128-57b0-ac9d-723678d9b239',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '3ad26df7-6345-5f85-aac1-2306cf10eaed',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: 'b49b1a1c-2f6b-52a3-836b-ece89c698627',
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'1bdc52a0-0339-5360-ba17-b615250ef730',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Favorites',
|
||||
name: 'favorites',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9a5d4ae3-6f45-593f-923d-bcd16ede4345',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '94f43ec4-d2d1-5a50-b414-78da27d24258',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '547c93e4-c9e7-5880-b251-46230fb59bfa',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ca687361-ac2b-5722-9ad9-cd37427d087a',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '47deea5c-8559-5ae7-991a-60c9f7102f86',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: 'IconMail',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
@@ -628,6 +1329,106 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '8da8e149-b60e-52e2-878d-73d05c8312b2',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '824e930c-ce9f-5267-bebd-f6db215c3c14',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '1f5df24b-8248-59cf-8bb6-362de121d2e4',
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'b01f6937-4859-587b-b3df-92e0a6f65e93',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Favorites',
|
||||
name: 'favorites',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'f4e6fd66-83bb-5f2d-b1ec-a3db2e0ca32d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'd16a6387-273b-563c-a6cc-2cb4524b9b7b',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9c87e1b8-74a2-5833-9237-281d561d2a7d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '244a7841-9e45-5ed1-a830-83e73c1f13fa',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'23508618-7ed8-519e-9fee-a57b94dc98e9',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'eab71eb1-b457-555a-8a2b-f292cef1671d',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'20500ff3-2410-5bfc-b2e7-804785c07770',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '305f1890-04ba-5101-b18f-a1ca25a1d8a7',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
},
|
||||
],
|
||||
icon: 'IconUser',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
|
||||
+5
-30
@@ -13,42 +13,17 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
|
||||
expect(manifest).not.toBeNull();
|
||||
|
||||
const { sources: _sources, ...sanitizedManifest } = manifest;
|
||||
|
||||
expect(normalizeManifestForComparison(sanitizedManifest)).toEqual(
|
||||
normalizeManifestForComparison(EXPECTED_MANIFEST),
|
||||
);
|
||||
|
||||
for (const fn of manifest.logicFunctions) {
|
||||
expect(fn.builtHandlerChecksum).toBeDefined();
|
||||
expect(fn.builtHandlerChecksum).not.toBeNull();
|
||||
expect(typeof fn.builtHandlerChecksum).toBe('string');
|
||||
}
|
||||
|
||||
for (const component of manifest.frontComponents ?? []) {
|
||||
expect(component.builtComponentChecksum).toBeDefined();
|
||||
expect(component.builtComponentChecksum).not.toBeNull();
|
||||
expect(typeof component.builtComponentChecksum).toBe('string');
|
||||
}
|
||||
});
|
||||
|
||||
it('should have correct application config', async () => {
|
||||
const manifest = await fs.readJson(manifestOutputPath);
|
||||
|
||||
expect(manifest?.application.displayName).toBe('Rich App');
|
||||
expect(manifest?.application.description).toBe('A simple rich app');
|
||||
});
|
||||
|
||||
it('should load all entity types', async () => {
|
||||
const manifest = await fs.readJson(manifestOutputPath);
|
||||
|
||||
expect(manifest.objects).toHaveLength(4);
|
||||
expect(manifest.logicFunctions).toHaveLength(6);
|
||||
expect(manifest.frontComponents).toHaveLength(4);
|
||||
expect(manifest.roles).toHaveLength(2);
|
||||
expect(manifest.fields).toHaveLength(6);
|
||||
expect(manifest.fields).toHaveLength(26);
|
||||
expect(manifest.views).toHaveLength(3);
|
||||
expect(manifest.navigationMenuItems).toHaveLength(3);
|
||||
|
||||
expect(normalizeManifestForComparison(manifest)).toEqual(
|
||||
normalizeManifestForComparison(EXPECTED_MANIFEST),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+208
-4
@@ -1,6 +1,10 @@
|
||||
import { FieldType } from '@/sdk';
|
||||
import type { Manifest } from 'twenty-shared/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
export const EXPECTED_MANIFEST: Manifest = {
|
||||
application: {
|
||||
@@ -9,13 +13,113 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
description: 'An app with all entities at root level',
|
||||
icon: 'IconFolder',
|
||||
defaultRoleUniversalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000002',
|
||||
packageJsonChecksum: '93ae1e2eb3db18351d06f43550700dcc',
|
||||
yarnLockChecksum: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
packageJsonChecksum: '[checksum]',
|
||||
yarnLockChecksum: '[checksum]',
|
||||
apiClientChecksum: null,
|
||||
},
|
||||
skills: [],
|
||||
publicAssets: [],
|
||||
fields: [],
|
||||
fields: [
|
||||
{
|
||||
name: 'targetMyNote',
|
||||
label: 'MyNote',
|
||||
description: 'MyNote My note',
|
||||
icon: 'IconTimelineEvent',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '7e28b1ea-9a94-5d36-bc03-3f12f476867a',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
|
||||
},
|
||||
{
|
||||
name: 'targetMyNote',
|
||||
label: 'MyNote',
|
||||
description: 'MyNote My note',
|
||||
icon: 'IconHeart',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '698f12fe-3feb-55e3-b6cc-73375f1a8ae6',
|
||||
objectUniversalIdentifier: '20202020-ab56-4e05-92a3-e2414a499860',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ed80c73b-25ff-5a3c-99c7-e27fa2f2611e',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldType.RELATION,
|
||||
},
|
||||
{
|
||||
name: 'targetMyNote',
|
||||
label: 'MyNote',
|
||||
description: 'MyNote My note',
|
||||
icon: 'IconFileImport',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'56a872c8-7a1e-563d-8772-3978c2c5c220',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphId: '20202020-f634-435d-ab8d-e1168b375c69',
|
||||
},
|
||||
{
|
||||
name: 'targetMyNote',
|
||||
label: 'MyNote',
|
||||
description: 'MyNote My note',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphId: '20202020-f635-435d-ab8d-e1168b375c70',
|
||||
},
|
||||
{
|
||||
name: 'targetMyNote',
|
||||
label: 'MyNote',
|
||||
description: 'MyNote My note',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
morphId: '20202020-f636-435d-ab8d-e1168b375c71',
|
||||
},
|
||||
],
|
||||
objects: [
|
||||
{
|
||||
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
@@ -110,6 +214,106 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'fa60f87d-15c8-5ff7-9da0-191fa14a922f',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '4a5d7791-ddc9-53ee-bc46-699da7373290',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '0f00bc3b-f313-5206-8496-842af20a538a',
|
||||
},
|
||||
{
|
||||
name: 'timelineActivities',
|
||||
label: 'Timeline Activities',
|
||||
description: 'My notes tied to the MyNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'7e28b1ea-9a94-5d36-bc03-3f12f476867a',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
},
|
||||
{
|
||||
name: 'favorites',
|
||||
label: 'Favorites',
|
||||
description: 'My notes tied to the MyNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: 'ed80c73b-25ff-5a3c-99c7-e27fa2f2611e',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'698f12fe-3feb-55e3-b6cc-73375f1a8ae6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-ab56-4e05-92a3-e2414a499860',
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
label: 'Attachments',
|
||||
description: 'My notes tied to the MyNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '56a872c8-7a1e-563d-8772-3978c2c5c220',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
},
|
||||
{
|
||||
name: 'noteTargets',
|
||||
label: 'Note Targets',
|
||||
description: 'My notes tied to the MyNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
},
|
||||
{
|
||||
name: 'taskTargets',
|
||||
label: 'Task Targets',
|
||||
description: 'My notes tied to the MyNote',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isNullable: false,
|
||||
type: FieldType.RELATION,
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: 'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
+3
-35
@@ -16,43 +16,11 @@ export const defineManifestTests = (appPath: string): void => {
|
||||
|
||||
it('should have correct manifest content', async () => {
|
||||
const manifestPath = join(appPath, '.twenty/output/manifest.json');
|
||||
const manifest: Manifest = await fs.readJSON(manifestPath);
|
||||
|
||||
expect(manifest.application).toEqual(EXPECTED_MANIFEST.application);
|
||||
expect(manifest.objects).toEqual(EXPECTED_MANIFEST.objects);
|
||||
|
||||
expect(
|
||||
normalizeManifestForComparison({
|
||||
logicFunctions: manifest.logicFunctions,
|
||||
}).logicFunctions,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({
|
||||
logicFunctions: EXPECTED_MANIFEST.logicFunctions,
|
||||
}).logicFunctions,
|
||||
const manifest: Manifest = normalizeManifestForComparison(
|
||||
await fs.readJSON(manifestPath),
|
||||
);
|
||||
|
||||
for (const fn of manifest.logicFunctions) {
|
||||
expect(fn.builtHandlerChecksum).toBeDefined();
|
||||
expect(fn.builtHandlerChecksum).not.toBeNull();
|
||||
expect(typeof fn.builtHandlerChecksum).toBe('string');
|
||||
}
|
||||
|
||||
expect(
|
||||
normalizeManifestForComparison({
|
||||
frontComponents: manifest.frontComponents,
|
||||
}).frontComponents,
|
||||
).toEqual(
|
||||
normalizeManifestForComparison({
|
||||
frontComponents: EXPECTED_MANIFEST.frontComponents,
|
||||
}).frontComponents,
|
||||
);
|
||||
|
||||
for (const component of manifest.frontComponents ?? []) {
|
||||
expect(component.builtComponentChecksum).toBeDefined();
|
||||
expect(component.builtComponentChecksum).not.toBeNull();
|
||||
expect(typeof component.builtComponentChecksum).toBe('string');
|
||||
}
|
||||
expect(manifest.roles).toEqual(EXPECTED_MANIFEST.roles);
|
||||
expect(manifest).toEqual(EXPECTED_MANIFEST);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+14
-14
@@ -1,21 +1,21 @@
|
||||
// Loose type for JSON manifest imports where enum values are inferred as strings
|
||||
type JsonManifestInput = {
|
||||
logicFunctions?: Array<{
|
||||
builtHandlerChecksum?: string | null;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
frontComponents?: Array<{
|
||||
builtComponentChecksum?: string;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
|
||||
// Replace dynamic checksum values with a placeholder for consistent comparisons
|
||||
export const normalizeManifestForComparison = <T extends JsonManifestInput>(
|
||||
export const normalizeManifestForComparison = <T extends Manifest>(
|
||||
manifest: T,
|
||||
): T => ({
|
||||
...manifest,
|
||||
application: {
|
||||
...manifest.application,
|
||||
yarnLockChecksum: manifest.application.yarnLockChecksum
|
||||
? '[checksum]'
|
||||
: null,
|
||||
packageJsonChecksum: manifest.application.packageJsonChecksum
|
||||
? '[checksum]'
|
||||
: null,
|
||||
apiClientChecksum: manifest.application.apiClientChecksum
|
||||
? '[checksum]'
|
||||
: null,
|
||||
},
|
||||
logicFunctions: manifest.logicFunctions?.map((fn) => ({
|
||||
...fn,
|
||||
builtHandlerChecksum: fn.builtHandlerChecksum ? '[checksum]' : null,
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
import { createTypecheckPlugin } from '@/cli/utilities/build/common/typecheck-plugin';
|
||||
import * as esbuild from 'esbuild';
|
||||
import path from 'path';
|
||||
import { OUTPUT_DIR, NODE_ESM_CJS_BANNER } from 'twenty-shared/application';
|
||||
import {
|
||||
OUTPUT_DIR,
|
||||
NODE_ESM_CJS_BANNER,
|
||||
GENERATED_DIR,
|
||||
} from 'twenty-shared/application';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
export const LOGIC_FUNCTION_EXTERNAL_MODULES: string[] = [
|
||||
@@ -199,7 +203,7 @@ const createSdkGeneratedResolverPlugin = (appPath: string): esbuild.Plugin => ({
|
||||
appPath,
|
||||
'node_modules',
|
||||
'twenty-sdk',
|
||||
'generated',
|
||||
GENERATED_DIR,
|
||||
'index.ts',
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
TARGET_FUNCTION_TO_ENTITY_KEY_MAPPING,
|
||||
} from '@/cli/utilities/build/manifest/manifest-extract-config';
|
||||
import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest-extract-config-from-file';
|
||||
import { injectDefaultFieldsInObjectFields } from '@/cli/utilities/build/manifest/utils/inject-default-fields-in-object-fields';
|
||||
import {
|
||||
type ApplicationConfig,
|
||||
type FrontComponentConfig,
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
import { getInputSchemaFromSourceCode } from 'twenty-shared/logic-function';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { getDefaultFieldsInObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-fields-in-object-fields';
|
||||
|
||||
const loadSources = async (appPath: string): Promise<string[]> => {
|
||||
return await glob(['**/*.ts', '**/*.tsx'], {
|
||||
@@ -120,13 +120,14 @@ export const buildManifest = async (
|
||||
filePath,
|
||||
});
|
||||
|
||||
const objectFieldsWithDefaultFields = injectDefaultFieldsInObjectFields(
|
||||
extract.config,
|
||||
);
|
||||
const {
|
||||
objectFields: objectFieldsWithDefaults,
|
||||
fields: reverseRelationFields,
|
||||
} = getDefaultFieldsInObjectFields(extract.config);
|
||||
|
||||
const labelIdentifierFieldMetadataUniversalIdentifier =
|
||||
extract.config.labelIdentifierFieldMetadataUniversalIdentifier ??
|
||||
objectFieldsWithDefaultFields.find((field) => field.name === 'name')
|
||||
objectFieldsWithDefaults.find((field) => field.name === 'name')
|
||||
?.universalIdentifier;
|
||||
|
||||
if (!labelIdentifierFieldMetadataUniversalIdentifier) {
|
||||
@@ -138,11 +139,12 @@ export const buildManifest = async (
|
||||
|
||||
const objectManifest: ObjectManifest = {
|
||||
...extract.config,
|
||||
fields: objectFieldsWithDefaultFields,
|
||||
fields: objectFieldsWithDefaults,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
};
|
||||
|
||||
objects.push(objectManifest);
|
||||
fields.push(...reverseRelationFields);
|
||||
|
||||
errors.push(...extract.errors);
|
||||
objectsFilePaths.push(relativePath);
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import crypto from 'crypto';
|
||||
import { relative } from 'path';
|
||||
import { type Manifest, OUTPUT_DIR } from 'twenty-shared/application';
|
||||
import {
|
||||
type Manifest,
|
||||
OUTPUT_DIR,
|
||||
API_CLIENT_DIR,
|
||||
} from 'twenty-shared/application';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import type { EntityFilePaths } from '@/cli/utilities/build/manifest/manifest-extract-config';
|
||||
@@ -97,7 +101,7 @@ export const manifestUpdateChecksums = ({
|
||||
|
||||
if (
|
||||
fileFolder === FileFolder.Dependencies &&
|
||||
rootBuiltPath.startsWith('api-client/')
|
||||
rootBuiltPath.startsWith(`${API_CLIENT_DIR}/`)
|
||||
) {
|
||||
const entry = builtFileInfos.get(builtPath);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path, { relative } from 'path';
|
||||
import chokidar, { type FSWatcher } from 'chokidar';
|
||||
import { type EventName } from 'chokidar/handler.js';
|
||||
import { ASSETS_DIR } from 'twenty-shared/application';
|
||||
import { ASSETS_DIR, GENERATED_DIR } from 'twenty-shared/application';
|
||||
|
||||
export type ManifestWatcherOptions = {
|
||||
appPath: string;
|
||||
@@ -10,7 +10,7 @@ export type ManifestWatcherOptions = {
|
||||
|
||||
const IGNORED_DIRECTORY_NAMES = new Set([
|
||||
'node_modules',
|
||||
'generated',
|
||||
GENERATED_DIR,
|
||||
'dist',
|
||||
'.twenty',
|
||||
]);
|
||||
|
||||
+53
-23
@@ -1,7 +1,9 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { injectDefaultFieldsInObjectFields } from '@/cli/utilities/build/manifest/utils/inject-default-fields-in-object-fields';
|
||||
import { getDefaultFieldsInObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-fields-in-object-fields';
|
||||
import { getDefaultObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-object-fields';
|
||||
import type { ObjectConfig } from '@/sdk/objects/object-config';
|
||||
import { getDefaultRelationObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-relation-object-fields';
|
||||
import { type ObjectFieldManifest } from 'twenty-shared/application';
|
||||
|
||||
const baseObjectConfig: ObjectConfig = {
|
||||
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
@@ -12,16 +14,25 @@ const baseObjectConfig: ObjectConfig = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
describe('injectDefaultFieldsInObjectFields', () => {
|
||||
describe('getDefaultFieldsInObjectFields', () => {
|
||||
it('should return all default fields when objectConfig has no fields', () => {
|
||||
const result = injectDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
const { objectFields, fields } =
|
||||
getDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
const defaultFields = getDefaultObjectFields(baseObjectConfig);
|
||||
const {
|
||||
objectFields: defaultRelationObjectFields,
|
||||
fields: expectedReverseFields,
|
||||
} = getDefaultRelationObjectFields(baseObjectConfig);
|
||||
|
||||
expect(result).toEqual(defaultFields);
|
||||
expect(objectFields).toEqual([
|
||||
...defaultFields,
|
||||
...defaultRelationObjectFields,
|
||||
]);
|
||||
expect(fields).toEqual(expectedReverseFields);
|
||||
});
|
||||
|
||||
it('should preserve custom fields and append missing default fields', () => {
|
||||
const customField = {
|
||||
const customField: ObjectFieldManifest = {
|
||||
universalIdentifier: '11111111-1111-1111-1111-111111111111',
|
||||
name: 'customField',
|
||||
label: 'Custom Field',
|
||||
@@ -33,15 +44,19 @@ describe('injectDefaultFieldsInObjectFields', () => {
|
||||
fields: [customField],
|
||||
};
|
||||
|
||||
const result = injectDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getDefaultObjectFields(objectConfig);
|
||||
const { objectFields: defaultRelationObjectFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
|
||||
expect(result[0]).toEqual(customField);
|
||||
expect(result).toHaveLength(1 + defaultFields.length);
|
||||
expect(objectFields[0]).toEqual(customField);
|
||||
expect(objectFields).toHaveLength(
|
||||
1 + defaultFields.length + defaultRelationObjectFields.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not inject a default field when a field with the same name exists', () => {
|
||||
const customIdField = {
|
||||
const customIdField: ObjectFieldManifest = {
|
||||
universalIdentifier: '22222222-2222-2222-2222-222222222222',
|
||||
name: 'id',
|
||||
label: 'Custom Id',
|
||||
@@ -53,16 +68,16 @@ describe('injectDefaultFieldsInObjectFields', () => {
|
||||
fields: [customIdField],
|
||||
};
|
||||
|
||||
const result = injectDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
const idFields = result.filter((f) => f.name === 'id');
|
||||
const idFields = objectFields.filter((f) => f.name === 'id');
|
||||
|
||||
expect(idFields).toHaveLength(1);
|
||||
expect(idFields[0]).toEqual(customIdField);
|
||||
});
|
||||
|
||||
it('should skip multiple default fields when overridden by custom fields', () => {
|
||||
const customFields = [
|
||||
const customFields: ObjectFieldManifest[] = [
|
||||
{
|
||||
universalIdentifier: '33333333-3333-3333-3333-333333333333',
|
||||
name: 'id',
|
||||
@@ -88,23 +103,28 @@ describe('injectDefaultFieldsInObjectFields', () => {
|
||||
fields: customFields,
|
||||
};
|
||||
|
||||
const result = injectDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getDefaultObjectFields(objectConfig);
|
||||
const { objectFields: defaultRelationObjectFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
const overriddenCount = defaultFields.filter((df) =>
|
||||
customFields.some((cf) => cf.name === df.name),
|
||||
).length;
|
||||
|
||||
expect(result).toHaveLength(
|
||||
customFields.length + defaultFields.length - overriddenCount,
|
||||
expect(objectFields).toHaveLength(
|
||||
customFields.length +
|
||||
defaultFields.length +
|
||||
defaultRelationObjectFields.length -
|
||||
overriddenCount,
|
||||
);
|
||||
|
||||
expect(result.filter((f) => f.name === 'id')).toHaveLength(1);
|
||||
expect(result.filter((f) => f.name === 'name')).toHaveLength(1);
|
||||
expect(result.filter((f) => f.name === 'position')).toHaveLength(1);
|
||||
expect(objectFields.filter((f) => f.name === 'id')).toHaveLength(1);
|
||||
expect(objectFields.filter((f) => f.name === 'name')).toHaveLength(1);
|
||||
expect(objectFields.filter((f) => f.name === 'position')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should place custom fields before default fields in the result', () => {
|
||||
const customField = {
|
||||
const customField: ObjectFieldManifest = {
|
||||
universalIdentifier: '66666666-6666-6666-6666-666666666666',
|
||||
name: 'customField',
|
||||
label: 'Custom Field',
|
||||
@@ -116,13 +136,13 @@ describe('injectDefaultFieldsInObjectFields', () => {
|
||||
fields: [customField],
|
||||
};
|
||||
|
||||
const result = injectDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
expect(result[0]).toEqual(customField);
|
||||
expect(objectFields[0]).toEqual(customField);
|
||||
});
|
||||
|
||||
it('should not mutate the original objectConfig fields array', () => {
|
||||
const customField = {
|
||||
const customField: ObjectFieldManifest = {
|
||||
universalIdentifier: '77777777-7777-7777-7777-777777777777',
|
||||
name: 'customField',
|
||||
label: 'Custom Field',
|
||||
@@ -136,8 +156,18 @@ describe('injectDefaultFieldsInObjectFields', () => {
|
||||
|
||||
const originalLength = objectConfig.fields.length;
|
||||
|
||||
injectDefaultFieldsInObjectFields(objectConfig);
|
||||
getDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
expect(objectConfig.fields).toHaveLength(originalLength);
|
||||
});
|
||||
|
||||
it('should return reverse relation fields for each default relation', () => {
|
||||
const { fields } = getDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
|
||||
expect(fields).toHaveLength(5);
|
||||
|
||||
const fieldNames = fields.map((f) => f.name);
|
||||
|
||||
expect(fieldNames).toContain('targetTestObject');
|
||||
});
|
||||
});
|
||||
+56
-7
@@ -22,7 +22,7 @@ describe('getDefaultObjectFields', () => {
|
||||
it('should return an array of 9 default fields', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
|
||||
expect(fields).toHaveLength(7);
|
||||
expect(fields).toHaveLength(9);
|
||||
});
|
||||
|
||||
it('should include an id field with UUID type', () => {
|
||||
@@ -59,15 +59,26 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include updatedAt and deletedAt fields with DATE_TIME type', () => {
|
||||
it('should include createdAt, updatedAt and deletedAt fields with DATE_TIME type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const updatedAtFields = fields.filter(
|
||||
(field) => field.name === 'updatedAt',
|
||||
);
|
||||
const createdAtField = fields.find((field) => field.name === 'createdAt');
|
||||
const updatedAtField = fields.find((field) => field.name === 'updatedAt');
|
||||
const deletedAtField = fields.find((field) => field.name === 'deletedAt');
|
||||
|
||||
expect(updatedAtFields).toHaveLength(1);
|
||||
expect(updatedAtFields[0]).toEqual({
|
||||
expect(createdAtField).toBeDefined();
|
||||
expect(createdAtField).toEqual({
|
||||
name: 'createdAt',
|
||||
label: 'Creation date',
|
||||
description: 'Creation date',
|
||||
icon: 'IconCalendar',
|
||||
isNullable: false,
|
||||
defaultValue: 'now',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: expectedUniversalId('createdAt'),
|
||||
});
|
||||
|
||||
expect(updatedAtField).toBeDefined();
|
||||
expect(updatedAtField).toEqual({
|
||||
name: 'updatedAt',
|
||||
label: 'Last update',
|
||||
description: 'Last time the record was changed',
|
||||
@@ -121,6 +132,42 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should include a position field with POSITION type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const positionField = fields.find((field) => field.name === 'position');
|
||||
|
||||
expect(positionField).toBeDefined();
|
||||
expect(positionField).toEqual({
|
||||
name: 'position',
|
||||
label: 'Position',
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
defaultValue: 0,
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: expectedUniversalId('position'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should include a searchVector field with TS_VECTOR type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const searchVectorField = fields.find(
|
||||
(field) => field.name === 'searchVector',
|
||||
);
|
||||
|
||||
expect(searchVectorField).toBeDefined();
|
||||
expect(searchVectorField).toEqual({
|
||||
name: 'searchVector',
|
||||
label: 'Search vector',
|
||||
description: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: expectedUniversalId('searchVector'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate deterministic universalIdentifiers based on objectConfig', () => {
|
||||
const firstResult = getDefaultObjectFields(mockObjectConfig);
|
||||
const secondResult = getDefaultObjectFields(mockObjectConfig);
|
||||
@@ -160,6 +207,8 @@ describe('getDefaultObjectFields', () => {
|
||||
'deletedAt',
|
||||
'createdBy',
|
||||
'updatedBy',
|
||||
'position',
|
||||
'searchVector',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import type { ObjectConfig } from '@/sdk/objects/object-config';
|
||||
import { getDefaultObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-object-fields';
|
||||
import { getDefaultRelationObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-relation-object-fields';
|
||||
import {
|
||||
type FieldManifest,
|
||||
type ObjectFieldManifest,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
export const getDefaultFieldsInObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
const defaultObjectFields = getDefaultObjectFields(objectConfig);
|
||||
const { objectFields: defaultRelationObjectFields, fields: reverseFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
|
||||
const objectConfigFieldNames = (objectConfig.fields ?? []).map((f) => f.name);
|
||||
|
||||
const objectFieldsWithDefaults = [...objectConfig.fields];
|
||||
|
||||
for (const defaultField of defaultObjectFields) {
|
||||
if (!objectConfigFieldNames.includes(defaultField.name)) {
|
||||
objectFieldsWithDefaults.push(defaultField);
|
||||
}
|
||||
}
|
||||
|
||||
for (const defaultRelationField of defaultRelationObjectFields) {
|
||||
if (!objectConfigFieldNames.includes(defaultRelationField.name)) {
|
||||
objectFieldsWithDefaults.push(defaultRelationField);
|
||||
}
|
||||
}
|
||||
|
||||
return { objectFields: objectFieldsWithDefaults, fields: reverseFields };
|
||||
};
|
||||
+39
-9
@@ -1,12 +1,12 @@
|
||||
import { type ObjectManifest } from 'twenty-shared/application';
|
||||
import type { ObjectConfig } from '@/sdk/objects/object-config';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { generateDefaultFieldUniversalIdentifier } from '@/cli/utilities/build/manifest/utils/generate-default-field-universal-identifier';
|
||||
import { type ObjectFieldManifest } from 'twenty-shared/application';
|
||||
|
||||
export const getDefaultObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): ObjectManifest['fields'] => {
|
||||
const idField = {
|
||||
): ObjectFieldManifest[] => {
|
||||
const idField: ObjectFieldManifest = {
|
||||
name: 'id',
|
||||
label: 'Id',
|
||||
description: 'Id',
|
||||
@@ -20,7 +20,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const nameField = {
|
||||
const nameField: ObjectFieldManifest = {
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'Name',
|
||||
@@ -34,7 +34,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const createdAtField = {
|
||||
const createdAtField: ObjectFieldManifest = {
|
||||
name: 'createdAt',
|
||||
label: 'Creation date',
|
||||
description: 'Creation date',
|
||||
@@ -48,7 +48,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const updatedAtField = {
|
||||
const updatedAtField: ObjectFieldManifest = {
|
||||
name: 'updatedAt',
|
||||
label: 'Last update',
|
||||
description: 'Last time the record was changed',
|
||||
@@ -62,7 +62,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const deletedAtField = {
|
||||
const deletedAtField: ObjectFieldManifest = {
|
||||
name: 'deletedAt',
|
||||
label: 'Deleted at',
|
||||
description: 'Deletion date',
|
||||
@@ -76,7 +76,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const createdByField = {
|
||||
const createdByField: ObjectFieldManifest = {
|
||||
name: 'createdBy',
|
||||
label: 'Created by',
|
||||
description: 'The creator of the record',
|
||||
@@ -90,7 +90,7 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const updatedByField = {
|
||||
const updatedByField: ObjectFieldManifest = {
|
||||
name: 'updatedBy',
|
||||
label: 'Updated by',
|
||||
description: 'The workspace member who last updated the record',
|
||||
@@ -104,6 +104,34 @@ export const getDefaultObjectFields = (
|
||||
}),
|
||||
};
|
||||
|
||||
const positionField: ObjectFieldManifest = {
|
||||
name: 'position',
|
||||
label: 'Position',
|
||||
description: 'Position',
|
||||
icon: 'IconHierarchy2',
|
||||
isNullable: false,
|
||||
defaultValue: 0,
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
objectConfig,
|
||||
fieldName: 'position',
|
||||
}),
|
||||
};
|
||||
|
||||
const searchVectorField: ObjectFieldManifest = {
|
||||
name: 'searchVector',
|
||||
label: 'Search vector',
|
||||
icon: 'IconSearch',
|
||||
description: 'Search vector',
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
objectConfig,
|
||||
fieldName: 'searchVector',
|
||||
}),
|
||||
};
|
||||
|
||||
return [
|
||||
idField,
|
||||
nameField,
|
||||
@@ -112,5 +140,7 @@ export const getDefaultObjectFields = (
|
||||
deletedAtField,
|
||||
createdByField,
|
||||
updatedByField,
|
||||
positionField,
|
||||
searchVectorField,
|
||||
];
|
||||
};
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
import { generateDefaultFieldUniversalIdentifier } from '@/cli/utilities/build/manifest/utils/generate-default-field-universal-identifier';
|
||||
import { RelationType } from '@/sdk';
|
||||
import type { ObjectConfig } from '@/sdk/objects/object-config';
|
||||
import {
|
||||
type FieldManifest,
|
||||
type ObjectFieldManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
type DefaultRelationConfig = {
|
||||
fieldName: string;
|
||||
label: string;
|
||||
targetFieldName: (objectConfig: ObjectConfig) => string;
|
||||
targetLabel: (objectConfig: ObjectConfig) => string;
|
||||
icon: string;
|
||||
targetIcon: string;
|
||||
standardObjectKey: keyof typeof STANDARD_OBJECTS;
|
||||
} & (
|
||||
| { targetFieldType: FieldMetadataType.RELATION }
|
||||
| { targetFieldType: FieldMetadataType.MORPH_RELATION; morphId: string }
|
||||
);
|
||||
|
||||
const DEFAULT_DEFAULT_RELATION = {
|
||||
targetFieldName: (objectConfig: ObjectConfig) =>
|
||||
`target${capitalize(objectConfig.nameSingular)}`,
|
||||
targetLabel: (objectConfig: ObjectConfig) =>
|
||||
capitalize(objectConfig.nameSingular),
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
};
|
||||
|
||||
const DEFAULT_RELATION_CONFIGS = [
|
||||
{
|
||||
...DEFAULT_DEFAULT_RELATION,
|
||||
fieldName: 'timelineActivities',
|
||||
label: 'Timeline Activities',
|
||||
targetIcon: 'IconTimelineEvent',
|
||||
targetFieldType: FieldMetadataType.MORPH_RELATION,
|
||||
standardObjectKey: 'timelineActivity',
|
||||
morphId: STANDARD_OBJECTS.timelineActivity.morphIds.targetMorphId.morphId,
|
||||
},
|
||||
{
|
||||
...DEFAULT_DEFAULT_RELATION,
|
||||
fieldName: 'favorites',
|
||||
label: 'Favorites',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
targetIcon: 'IconHeart',
|
||||
targetFieldType: FieldMetadataType.RELATION,
|
||||
standardObjectKey: 'favorite',
|
||||
},
|
||||
{
|
||||
...DEFAULT_DEFAULT_RELATION,
|
||||
fieldName: 'attachments',
|
||||
label: 'Attachments',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
targetIcon: 'IconFileImport',
|
||||
targetFieldType: FieldMetadataType.MORPH_RELATION,
|
||||
standardObjectKey: 'attachment',
|
||||
morphId: STANDARD_OBJECTS.attachment.morphIds.targetMorphId.morphId,
|
||||
},
|
||||
{
|
||||
...DEFAULT_DEFAULT_RELATION,
|
||||
fieldName: 'noteTargets',
|
||||
label: 'Note Targets',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
targetIcon: 'IconCheckbox',
|
||||
targetFieldType: FieldMetadataType.MORPH_RELATION,
|
||||
standardObjectKey: 'noteTarget',
|
||||
morphId: STANDARD_OBJECTS.noteTarget.morphIds.targetMorphId.morphId,
|
||||
},
|
||||
{
|
||||
...DEFAULT_DEFAULT_RELATION,
|
||||
fieldName: 'taskTargets',
|
||||
label: 'Task Targets',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
targetIcon: 'IconCheckbox',
|
||||
targetFieldType: FieldMetadataType.MORPH_RELATION,
|
||||
standardObjectKey: 'taskTarget',
|
||||
morphId: STANDARD_OBJECTS.taskTarget.morphIds.targetMorphId.morphId,
|
||||
},
|
||||
] as const satisfies DefaultRelationConfig[];
|
||||
|
||||
const buildReverseField = ({
|
||||
config,
|
||||
forwardFieldUniversalIdentifier,
|
||||
objectConfig,
|
||||
universalIdentifier,
|
||||
}: {
|
||||
config: DefaultRelationConfig;
|
||||
objectConfig: ObjectConfig;
|
||||
universalIdentifier: string;
|
||||
forwardFieldUniversalIdentifier: string;
|
||||
}): FieldManifest<typeof config.targetFieldType> => {
|
||||
const standardObject = STANDARD_OBJECTS[config.standardObjectKey];
|
||||
|
||||
const commonFields = {
|
||||
name: config.targetFieldName(objectConfig),
|
||||
label: config.targetLabel(objectConfig),
|
||||
description: `${config.targetLabel(objectConfig)} ${objectConfig.labelSingular}`,
|
||||
icon: config.targetIcon,
|
||||
isNullable: true,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: `target${capitalize(objectConfig.nameSingular)}Id`,
|
||||
},
|
||||
universalIdentifier,
|
||||
objectUniversalIdentifier: standardObject.universalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
forwardFieldUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
objectConfig.universalIdentifier,
|
||||
};
|
||||
|
||||
if (config.targetFieldType === FieldMetadataType.MORPH_RELATION) {
|
||||
return {
|
||||
...commonFields,
|
||||
type: config.targetFieldType,
|
||||
morphId: config.morphId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...commonFields,
|
||||
type: config.targetFieldType,
|
||||
};
|
||||
};
|
||||
|
||||
export const getDefaultRelationObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
const objectFields: ObjectFieldManifest[] = [];
|
||||
const fields: FieldManifest[] = [];
|
||||
|
||||
for (const config of DEFAULT_RELATION_CONFIGS) {
|
||||
const standardObject = STANDARD_OBJECTS[config.standardObjectKey];
|
||||
|
||||
const forwardFieldUniversalIdentifier =
|
||||
generateDefaultFieldUniversalIdentifier({
|
||||
objectConfig,
|
||||
fieldName: config.fieldName,
|
||||
});
|
||||
|
||||
const reverseFieldUniversalIdentifier =
|
||||
generateDefaultFieldUniversalIdentifier({
|
||||
objectConfig,
|
||||
fieldName: `${config.fieldName}Inverse`,
|
||||
});
|
||||
|
||||
const forwardField: ObjectFieldManifest = {
|
||||
name: config.fieldName,
|
||||
label: config.label,
|
||||
description: `${objectConfig.labelPlural} tied to the ${config.targetLabel(objectConfig)}`,
|
||||
icon: config.icon,
|
||||
isNullable: false,
|
||||
type: FieldMetadataType.RELATION,
|
||||
universalSettings: { relationType: RelationType.ONE_TO_MANY },
|
||||
universalIdentifier: forwardFieldUniversalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
reverseFieldUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
standardObject.universalIdentifier,
|
||||
};
|
||||
|
||||
const reverseField = buildReverseField({
|
||||
config,
|
||||
objectConfig,
|
||||
universalIdentifier: reverseFieldUniversalIdentifier,
|
||||
forwardFieldUniversalIdentifier,
|
||||
});
|
||||
|
||||
objectFields.push(forwardField);
|
||||
fields.push(reverseField);
|
||||
}
|
||||
|
||||
return { objectFields, fields };
|
||||
};
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import type { ObjectConfig } from '@/sdk/objects/object-config';
|
||||
import { getDefaultObjectFields } from '@/cli/utilities/build/manifest/utils/get-default-object-fields';
|
||||
|
||||
export const injectDefaultFieldsInObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
) => {
|
||||
const defaultObjectFields = getDefaultObjectFields(objectConfig);
|
||||
|
||||
const objectConfigFieldNames = (objectConfig.fields ?? []).map((f) => f.name);
|
||||
|
||||
const fieldsWithDefaultFields = [...objectConfig.fields];
|
||||
|
||||
for (const defaultField of defaultObjectFields) {
|
||||
if (!objectConfigFieldNames.includes(defaultField.name)) {
|
||||
fieldsWithDefaultFields.push(defaultField);
|
||||
}
|
||||
}
|
||||
|
||||
return fieldsWithDefaultFields;
|
||||
};
|
||||
@@ -5,6 +5,7 @@ import { join } from 'path';
|
||||
import {
|
||||
DEFAULT_API_KEY_NAME,
|
||||
DEFAULT_API_URL_NAME,
|
||||
GENERATED_DIR,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
export class ClientService {
|
||||
@@ -54,7 +55,7 @@ export class ClientService {
|
||||
}
|
||||
|
||||
private resolveGeneratedPath(appPath: string): string {
|
||||
return join(appPath, 'node_modules', 'twenty-sdk', 'generated');
|
||||
return join(appPath, 'node_modules', 'twenty-sdk', GENERATED_DIR);
|
||||
}
|
||||
|
||||
private async injectTwentyClient(output: string) {
|
||||
|
||||
+9
-5
@@ -6,7 +6,11 @@ import { FileUploader } from '@/cli/utilities/file/file-uploader';
|
||||
import crypto from 'crypto';
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { OUTPUT_DIR } from 'twenty-shared/application';
|
||||
import {
|
||||
OUTPUT_DIR,
|
||||
GENERATED_DIR,
|
||||
API_CLIENT_DIR,
|
||||
} from 'twenty-shared/application';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
const API_CLIENT_FILES = ['types.ts', 'schema.ts'];
|
||||
@@ -114,14 +118,14 @@ export class UploadFilesOrchestratorStep {
|
||||
appPath,
|
||||
'node_modules',
|
||||
'twenty-sdk',
|
||||
'generated',
|
||||
GENERATED_DIR,
|
||||
);
|
||||
|
||||
if (!(await fs.pathExists(generatedDir))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputDir = join(appPath, OUTPUT_DIR, 'api-client');
|
||||
const outputDir = join(appPath, OUTPUT_DIR, API_CLIENT_DIR);
|
||||
|
||||
await fs.ensureDir(outputDir);
|
||||
|
||||
@@ -137,8 +141,8 @@ export class UploadFilesOrchestratorStep {
|
||||
const content = await fs.readFile(absoluteSourcePath);
|
||||
const checksum = crypto.createHash('md5').update(content).digest('hex');
|
||||
|
||||
const builtPath = join(OUTPUT_DIR, 'api-client', fileName);
|
||||
const sourcePath = join('api-client', fileName);
|
||||
const builtPath = join(OUTPUT_DIR, API_CLIENT_DIR, fileName);
|
||||
const sourcePath = join(API_CLIENT_DIR, fileName);
|
||||
|
||||
this.state.steps.uploadFiles.output.builtFileInfos.set(builtPath, {
|
||||
checksum,
|
||||
|
||||
@@ -16,11 +16,7 @@ export default defineConfig({
|
||||
'src/**/__integration__/**/*.{test,spec}.{ts,tsx}',
|
||||
'src/**/*.{test,spec}.{ts,tsx}',
|
||||
],
|
||||
exclude: [
|
||||
'**/node_modules/**',
|
||||
'**/.git/**',
|
||||
'**/__e2e__/**',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '**/.git/**', '**/__e2e__/**'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.{ts,tsx}'],
|
||||
|
||||
+34
-18
@@ -1,23 +1,25 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type Manifest, type RoleManifest } from 'twenty-shared/application';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/build-from-to-all-universal-flat-entity-maps.util';
|
||||
import { computeApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
|
||||
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-application-sub-all-flat-entity-maps.util';
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { WorkspaceMigration } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration.type';
|
||||
@@ -48,32 +50,42 @@ export class ApplicationManifestMigrationService {
|
||||
}): Promise<WorkspaceMigration> {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const toAllUniversalFlatEntityMaps =
|
||||
computeApplicationManifestAllUniversalFlatEntityMaps({
|
||||
manifest,
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
now,
|
||||
});
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const cacheResult = await this.workspaceCacheService.getOrRecompute(
|
||||
workspaceId,
|
||||
[
|
||||
...APPLICATION_MANIFEST_METADATA_NAMES.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
),
|
||||
...Object.values(ALL_METADATA_NAME).map(getMetadataFlatEntityMapsKey),
|
||||
'featureFlagsMap',
|
||||
],
|
||||
);
|
||||
|
||||
const { featureFlagsMap, ...fromAllFlatEntityMaps } = cacheResult;
|
||||
const { featureFlagsMap, ...existingAllFlatEntityMaps } = cacheResult;
|
||||
|
||||
const fromToAllFlatEntityMaps = getSubApplicationFromToAllFlatEntityMaps({
|
||||
applicationId: ownerFlatApplication.id,
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
const toAllUniversalFlatEntityMaps =
|
||||
computeApplicationManifestAllUniversalFlatEntityMaps({
|
||||
manifest,
|
||||
ownerFlatApplication,
|
||||
now,
|
||||
});
|
||||
|
||||
const fromAllFlatEntityMaps = getApplicationSubAllFlatEntityMaps({
|
||||
applicationIds: [ownerFlatApplication.id],
|
||||
fromAllFlatEntityMaps: existingAllFlatEntityMaps,
|
||||
});
|
||||
|
||||
const dependencyAllFlatEntityMaps =
|
||||
ownerFlatApplication.universalIdentifier ===
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier
|
||||
? undefined
|
||||
: getApplicationSubAllFlatEntityMaps({
|
||||
applicationIds: [twentyStandardFlatApplication.id],
|
||||
fromAllFlatEntityMaps: existingAllFlatEntityMaps,
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromTo(
|
||||
{
|
||||
@@ -83,8 +95,12 @@ export class ApplicationManifestMigrationService {
|
||||
applicationUniversalIdentifier:
|
||||
ownerFlatApplication.universalIdentifier,
|
||||
},
|
||||
fromToAllFlatEntityMaps,
|
||||
fromToAllFlatEntityMaps: buildFromToAllUniversalFlatEntityMaps({
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
}),
|
||||
workspaceId,
|
||||
dependencyAllFlatEntityMaps,
|
||||
additionalCacheDataMaps: { featureFlagsMap },
|
||||
},
|
||||
);
|
||||
|
||||
+15
-9
@@ -3,22 +3,23 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PackageJson } from 'type-fest';
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
||||
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/services/application-manifest-migration.service';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/build-from-to-all-universal-flat-entity-maps.util';
|
||||
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-application-sub-all-flat-entity-maps.util';
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
|
||||
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
|
||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
@@ -152,7 +153,7 @@ export class ApplicationSyncService {
|
||||
);
|
||||
}
|
||||
|
||||
const flatEntityMapsCacheKeys = APPLICATION_MANIFEST_METADATA_NAMES.map(
|
||||
const flatEntityMapsCacheKeys = Object.values(ALL_METADATA_NAME).map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
);
|
||||
|
||||
@@ -163,11 +164,16 @@ export class ApplicationSyncService {
|
||||
|
||||
const { featureFlagsMap, ...fromAllFlatEntityMaps } = cacheResult;
|
||||
|
||||
const fromToAllFlatEntityMaps = getSubApplicationFromToAllFlatEntityMaps({
|
||||
applicationId: application.id,
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps:
|
||||
getEmptyApplicationManifestAllUniversalFlatEntityMaps(),
|
||||
const applicationFromAllFlatEntityMaps = getApplicationSubAllFlatEntityMaps(
|
||||
{
|
||||
applicationIds: [application.id],
|
||||
fromAllFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
const fromToAllFlatEntityMaps = buildFromToAllUniversalFlatEntityMaps({
|
||||
fromAllFlatEntityMaps: applicationFromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps: createEmptyAllFlatEntityMaps(),
|
||||
});
|
||||
|
||||
const validateAndBuildResult =
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { type FromToAllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/types/workspace-migration-orchestrator.type';
|
||||
|
||||
export const buildFromToAllUniversalFlatEntityMaps = ({
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
}: {
|
||||
fromAllFlatEntityMaps: AllFlatEntityMaps;
|
||||
toAllUniversalFlatEntityMaps: AllFlatEntityMaps;
|
||||
}): FromToAllUniversalFlatEntityMaps => {
|
||||
const fromToAllFlatEntityMaps: FromToAllUniversalFlatEntityMaps = {};
|
||||
|
||||
for (const metadataName of Object.values(ALL_METADATA_NAME)) {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
const fromFlatEntityMaps = fromAllFlatEntityMaps[flatEntityMapsKey];
|
||||
const toFlatEntityMaps = toAllUniversalFlatEntityMaps[flatEntityMapsKey];
|
||||
|
||||
const fromTo = {
|
||||
from: fromFlatEntityMaps,
|
||||
to: toFlatEntityMaps,
|
||||
};
|
||||
|
||||
// @ts-expect-error Metadata flat entity maps cache key and metadataName colliding
|
||||
fromToAllFlatEntityMaps[flatEntityMapsKey] = fromTo;
|
||||
}
|
||||
|
||||
return fromToAllFlatEntityMaps;
|
||||
};
|
||||
+182
-229
@@ -1,6 +1,6 @@
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
|
||||
import { type ApplicationManifestMetadataName } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util';
|
||||
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/utils/from-field-manifest-to-universal-flat-field-metadata.util';
|
||||
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/utils/from-front-component-manifest-to-universal-flat-front-component.util';
|
||||
@@ -18,140 +18,115 @@ import { fromViewFilterGroupManifestToUniversalFlatViewFilterGroup } from 'src/e
|
||||
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/utils/from-view-filter-manifest-to-universal-flat-view-filter.util';
|
||||
import { fromViewGroupManifestToUniversalFlatViewGroup } from 'src/engine/core-modules/application/utils/from-view-group-manifest-to-universal-flat-view-group.util';
|
||||
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/utils/from-view-manifest-to-universal-flat-view.util';
|
||||
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
import { addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-and-related-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
export type ApplicationManifestAllUniversalFlatEntityMaps = Pick<
|
||||
AllUniversalFlatEntityMaps,
|
||||
MetadataToFlatEntityMapsKey<ApplicationManifestMetadataName>
|
||||
>;
|
||||
|
||||
export type ApplicationManifestAllFlatEntityMaps = Pick<
|
||||
AllFlatEntityMaps,
|
||||
MetadataToFlatEntityMapsKey<ApplicationManifestMetadataName>
|
||||
>;
|
||||
import { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-maps-through-mutation-or-throw.util';
|
||||
|
||||
export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
|
||||
manifest,
|
||||
applicationUniversalIdentifier,
|
||||
ownerFlatApplication,
|
||||
now,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
now: string;
|
||||
}): ApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const allUniversalFlatEntityMaps =
|
||||
getEmptyApplicationManifestAllUniversalFlatEntityMaps();
|
||||
}): AllFlatEntityMaps => {
|
||||
const allUniversalFlatEntityMaps = createEmptyAllFlatEntityMaps();
|
||||
|
||||
const { universalIdentifier: applicationUniversalIdentifier } =
|
||||
ownerFlatApplication;
|
||||
|
||||
for (const objectManifest of manifest.objects) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'objectMetadata',
|
||||
universalFlatEntity: fromObjectManifestToUniversalFlatObjectMetadata({
|
||||
objectManifest,
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromObjectManifestToUniversalFlatObjectMetadata({
|
||||
objectManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
for (const fieldManifest of objectManifest.fields) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: {
|
||||
...fieldManifest,
|
||||
objectUniversalIdentifier: objectManifest.universalIdentifier,
|
||||
},
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
for (const fieldManifest of objectManifest.fields) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'fieldMetadata',
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: {
|
||||
...fieldManifest,
|
||||
objectUniversalIdentifier: objectManifest.universalIdentifier,
|
||||
},
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const fieldManifest of manifest.fields) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'fieldMetadata',
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: fieldManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromFieldManifestToUniversalFlatFieldMetadata({
|
||||
fieldManifest: fieldManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const logicFunctionManifest of manifest.logicFunctions) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'logicFunction',
|
||||
universalFlatEntity:
|
||||
fromLogicFunctionManifestToUniversalFlatLogicFunction({
|
||||
logicFunctionManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromLogicFunctionManifestToUniversalFlatLogicFunction({
|
||||
logicFunctionManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatLogicFunctionMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const frontComponentManifest of manifest.frontComponents) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'frontComponent',
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromFrontComponentManifestToUniversalFlatFrontComponent({
|
||||
frontComponentManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatFrontComponentMaps,
|
||||
});
|
||||
|
||||
if (frontComponentManifest.command) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromFrontComponentManifestToUniversalFlatFrontComponent({
|
||||
frontComponentManifest,
|
||||
fromCommandMenuItemManifestToUniversalFlatCommandMenuItem({
|
||||
commandMenuItemManifest: {
|
||||
...frontComponentManifest.command,
|
||||
frontComponentUniversalIdentifier:
|
||||
frontComponentManifest.universalIdentifier,
|
||||
},
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
|
||||
if (frontComponentManifest.command) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'commandMenuItem',
|
||||
universalFlatEntity:
|
||||
fromCommandMenuItemManifestToUniversalFlatCommandMenuItem({
|
||||
commandMenuItemManifest: {
|
||||
...frontComponentManifest.command,
|
||||
frontComponentUniversalIdentifier:
|
||||
frontComponentManifest.universalIdentifier,
|
||||
},
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatCommandMenuItemMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const roleManifest of manifest.roles) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'role',
|
||||
universalFlatEntity: fromRoleManifestToUniversalFlatRole({
|
||||
roleManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromRoleManifestToUniversalFlatRole({
|
||||
roleManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate: allUniversalFlatEntityMaps.flatRoleMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const skillManifest of manifest.skills ?? []) {
|
||||
@@ -169,157 +144,135 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({
|
||||
}
|
||||
|
||||
for (const viewManifest of manifest.views ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'view',
|
||||
universalFlatEntity: fromViewManifestToUniversalFlatView({
|
||||
viewManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromViewManifestToUniversalFlatView({
|
||||
viewManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate: allUniversalFlatEntityMaps.flatViewMaps,
|
||||
});
|
||||
|
||||
for (const viewFieldGroupManifest of viewManifest.fieldGroups ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'viewFieldGroup',
|
||||
universalFlatEntity:
|
||||
fromViewFieldGroupManifestToUniversalFlatViewFieldGroup({
|
||||
viewFieldGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromViewFieldGroupManifestToUniversalFlatViewFieldGroup({
|
||||
viewFieldGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatViewFieldGroupMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const viewFieldManifest of viewManifest.fields ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'viewField',
|
||||
universalFlatEntity: fromViewFieldManifestToUniversalFlatViewField({
|
||||
viewFieldManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromViewFieldManifestToUniversalFlatViewField({
|
||||
viewFieldManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatViewFieldMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const viewFilterGroupManifest of viewManifest.filterGroups ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'viewFilterGroup',
|
||||
universalFlatEntity:
|
||||
fromViewFilterGroupManifestToUniversalFlatViewFilterGroup({
|
||||
viewFilterGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromViewFilterGroupManifestToUniversalFlatViewFilterGroup({
|
||||
viewFilterGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatViewFilterGroupMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const viewFilterManifest of viewManifest.filters ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'viewFilter',
|
||||
universalFlatEntity: fromViewFilterManifestToUniversalFlatViewFilter({
|
||||
viewFilterManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromViewFilterManifestToUniversalFlatViewFilter({
|
||||
viewFilterManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatViewFilterMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const viewGroupManifest of viewManifest.groups ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'viewGroup',
|
||||
universalFlatEntity: fromViewGroupManifestToUniversalFlatViewGroup({
|
||||
viewGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromViewGroupManifestToUniversalFlatViewGroup({
|
||||
viewGroupManifest,
|
||||
viewUniversalIdentifier: viewManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatViewGroupMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const navigationMenuItemManifest of manifest.navigationMenuItems ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'navigationMenuItem',
|
||||
universalFlatEntity:
|
||||
fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem({
|
||||
navigationMenuItemManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const pageLayoutManifest of manifest.pageLayouts ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'pageLayout',
|
||||
universalFlatEntity: fromPageLayoutManifestToUniversalFlatPageLayout({
|
||||
pageLayoutManifest,
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem({
|
||||
navigationMenuItemManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatNavigationMenuItemMaps,
|
||||
});
|
||||
}
|
||||
|
||||
for (const pageLayoutManifest of manifest.pageLayouts ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity: fromPageLayoutManifestToUniversalFlatPageLayout({
|
||||
pageLayoutManifest,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatPageLayoutMaps,
|
||||
});
|
||||
|
||||
for (const pageLayoutTabManifest of pageLayoutManifest.tabs ?? []) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'pageLayoutTab',
|
||||
universalFlatEntity:
|
||||
fromPageLayoutTabManifestToUniversalFlatPageLayoutTab({
|
||||
pageLayoutTabManifest,
|
||||
pageLayoutUniversalIdentifier:
|
||||
pageLayoutManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate: allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromPageLayoutTabManifestToUniversalFlatPageLayoutTab({
|
||||
pageLayoutTabManifest,
|
||||
pageLayoutUniversalIdentifier:
|
||||
pageLayoutManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
for (const pageLayoutWidgetManifest of pageLayoutTabManifest.widgets ??
|
||||
[]) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow(
|
||||
{
|
||||
metadataName: 'pageLayoutWidget',
|
||||
universalFlatEntity:
|
||||
fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget({
|
||||
pageLayoutWidgetManifest,
|
||||
pageLayoutTabUniversalIdentifier:
|
||||
pageLayoutTabManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityAndRelatedMapsToMutate:
|
||||
allUniversalFlatEntityMaps,
|
||||
},
|
||||
);
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget({
|
||||
pageLayoutWidgetManifest,
|
||||
pageLayoutTabUniversalIdentifier:
|
||||
pageLayoutTabManifest.universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
allUniversalFlatEntityMaps.flatPageLayoutWidgetMaps,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-9
@@ -1,8 +1,8 @@
|
||||
import { type FieldManifest } from 'twenty-shared/application';
|
||||
import {
|
||||
type FieldManifest,
|
||||
type RelationFieldManifest,
|
||||
} from 'twenty-shared/application';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
FieldMetadataType,
|
||||
type RelationAndMorphRelationFieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
ApplicationException,
|
||||
@@ -14,12 +14,12 @@ import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
|
||||
const isRelationFieldManifest = (
|
||||
fieldManifest: FieldManifest<FieldMetadataType>,
|
||||
): fieldManifest is RelationFieldManifest =>
|
||||
fieldManifest: FieldManifest,
|
||||
): fieldManifest is FieldManifest<RelationAndMorphRelationFieldMetadataType> =>
|
||||
isMorphOrRelationFieldMetadataType(fieldManifest.type);
|
||||
|
||||
const getRelationTargetUniversalIdentifiers = (
|
||||
fieldManifest: FieldManifest<FieldMetadataType>,
|
||||
fieldManifest: FieldManifest,
|
||||
): {
|
||||
relationTargetFieldMetadataUniversalIdentifier: string | null;
|
||||
relationTargetObjectMetadataUniversalIdentifier: string | null;
|
||||
@@ -54,7 +54,7 @@ export const fromFieldManifestToUniversalFlatFieldMetadata = ({
|
||||
applicationUniversalIdentifier,
|
||||
now,
|
||||
}: {
|
||||
fieldManifest: FieldManifest<FieldMetadataType> & {
|
||||
fieldManifest: FieldManifest & {
|
||||
objectUniversalIdentifier: string;
|
||||
};
|
||||
applicationUniversalIdentifier: string;
|
||||
@@ -85,7 +85,10 @@ export const fromFieldManifestToUniversalFlatFieldMetadata = ({
|
||||
isNullable: fieldManifest.isNullable ?? true,
|
||||
isUnique: false,
|
||||
isLabelSyncedWithName: false,
|
||||
morphId: null,
|
||||
morphId:
|
||||
fieldManifest.type === FieldMetadataType.MORPH_RELATION
|
||||
? (fieldManifest.morphId ?? null)
|
||||
: null,
|
||||
objectMetadataUniversalIdentifier: fieldManifest.objectUniversalIdentifier,
|
||||
relationTargetFieldMetadataUniversalIdentifier,
|
||||
relationTargetObjectMetadataUniversalIdentifier,
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getSubFlatEntityMapsByApplicationIdsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-by-application-ids-or-throw.util';
|
||||
|
||||
export const getApplicationSubAllFlatEntityMaps = ({
|
||||
applicationIds,
|
||||
fromAllFlatEntityMaps,
|
||||
}: {
|
||||
applicationIds: string[];
|
||||
fromAllFlatEntityMaps: AllFlatEntityMaps;
|
||||
}): AllFlatEntityMaps => {
|
||||
const emptyAllFlatEntityMaps = createEmptyAllFlatEntityMaps();
|
||||
|
||||
for (const metadataName of Object.values(ALL_METADATA_NAME)) {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
const fromFlatEntityMaps = fromAllFlatEntityMaps[flatEntityMapsKey];
|
||||
|
||||
const applicationSubFlatEntityMaps =
|
||||
getSubFlatEntityMapsByApplicationIdsOrThrow<
|
||||
MetadataFlatEntity<typeof metadataName>
|
||||
>({
|
||||
applicationIds,
|
||||
flatEntityMaps: fromFlatEntityMaps,
|
||||
});
|
||||
|
||||
// @ts-expect-error Metadata flat entity maps cache key and metadataName colliding
|
||||
emptyAllFlatEntityMaps[flatEntityMapsKey] = applicationSubFlatEntityMaps;
|
||||
}
|
||||
|
||||
return emptyAllFlatEntityMaps;
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import { type ApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
|
||||
export const getEmptyApplicationManifestAllUniversalFlatEntityMaps =
|
||||
(): ApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const result = {} as ApplicationManifestAllUniversalFlatEntityMaps;
|
||||
|
||||
for (const metadataName of APPLICATION_MANIFEST_METADATA_NAMES) {
|
||||
const key = getMetadataFlatEntityMapsKey(metadataName);
|
||||
|
||||
result[key] = { byUniversalIdentifier: {} };
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { APPLICATION_MANIFEST_METADATA_NAMES } from 'src/engine/core-modules/application/constants/application-manifest-metadata-names.constant';
|
||||
import {
|
||||
type ApplicationManifestAllFlatEntityMaps,
|
||||
type ApplicationManifestAllUniversalFlatEntityMaps,
|
||||
} from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getSubFlatEntityMapsByApplicationIdsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-by-application-ids-or-throw.util';
|
||||
import { type AllUniversalFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/all-universal-flat-entity-maps.type';
|
||||
|
||||
export type FromToApplicationManifestAllUniversalFlatEntityMaps = {
|
||||
[P in keyof ApplicationManifestAllFlatEntityMaps]?: FromTo<
|
||||
AllUniversalFlatEntityMaps[P]
|
||||
>;
|
||||
};
|
||||
|
||||
export const getSubApplicationFromToAllFlatEntityMaps = ({
|
||||
applicationId,
|
||||
fromAllFlatEntityMaps,
|
||||
toAllUniversalFlatEntityMaps,
|
||||
}: {
|
||||
applicationId: string;
|
||||
fromAllFlatEntityMaps: ApplicationManifestAllFlatEntityMaps;
|
||||
toAllUniversalFlatEntityMaps: ApplicationManifestAllUniversalFlatEntityMaps;
|
||||
}): FromToApplicationManifestAllUniversalFlatEntityMaps => {
|
||||
const fromToAllFlatEntityMaps: FromToApplicationManifestAllUniversalFlatEntityMaps =
|
||||
{};
|
||||
|
||||
for (const metadataName of APPLICATION_MANIFEST_METADATA_NAMES) {
|
||||
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(metadataName);
|
||||
const fromFlatEntityMaps = fromAllFlatEntityMaps[flatEntityMapsKey];
|
||||
const toFlatEntityMaps = toAllUniversalFlatEntityMaps[flatEntityMapsKey];
|
||||
|
||||
const fromTo = {
|
||||
from: getSubFlatEntityMapsByApplicationIdsOrThrow<
|
||||
MetadataFlatEntity<typeof metadataName>
|
||||
>({
|
||||
applicationIds: [applicationId],
|
||||
flatEntityMaps: fromFlatEntityMaps,
|
||||
}),
|
||||
to: toFlatEntityMaps,
|
||||
};
|
||||
|
||||
// @ts-expect-error Metadata flat entity maps cache key and metadataName colliding
|
||||
fromToAllFlatEntityMaps[flatEntityMapsKey] = fromTo;
|
||||
}
|
||||
|
||||
return fromToAllFlatEntityMaps;
|
||||
};
|
||||
+3
-2
@@ -12,6 +12,7 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/
|
||||
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/logic-function-executor-tmpdir-folder';
|
||||
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import { HANDLER_NAME_REGEX } from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
|
||||
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import type { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
@@ -215,9 +216,9 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
builtFileAbsPath: string;
|
||||
handlerName: string;
|
||||
}) {
|
||||
if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(handlerName)) {
|
||||
if (!HANDLER_NAME_REGEX.test(handlerName)) {
|
||||
throw new Error(
|
||||
`Invalid handlerName "${handlerName}": must be a valid JavaScript identifier`,
|
||||
`Invalid handlerName "${handlerName}": must be a valid JavaScript identifier or dotted path`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
export const DEFAULT_BUILT_HANDLER_PATH = 'src/index.mjs';
|
||||
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_HANDLER_NAME = 'main';
|
||||
|
||||
// Matches a valid JS identifier or dotted member expression
|
||||
// e.g. "main", "handler", "default.config.handler", "exports.run"
|
||||
export const HANDLER_NAME_REGEX =
|
||||
/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/;
|
||||
|
||||
+4
-2
@@ -3,6 +3,8 @@ import { Field, InputType } from '@nestjs/graphql';
|
||||
import { IsObject, IsString, Matches } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import { HANDLER_NAME_REGEX } from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
|
||||
|
||||
@InputType()
|
||||
export class LogicFunctionSourceInput {
|
||||
@IsString()
|
||||
@@ -14,8 +16,8 @@ export class LogicFunctionSourceInput {
|
||||
toolInputSchema: object;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/, {
|
||||
message: 'handlerName must be a valid JavaScript identifier',
|
||||
@Matches(HANDLER_NAME_REGEX, {
|
||||
message: 'handlerName must be a valid JavaScript identifier or dotted path',
|
||||
})
|
||||
@Field({ nullable: false })
|
||||
handlerName: string;
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { HANDLER_NAME_REGEX } from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
|
||||
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
@InputType()
|
||||
@@ -54,8 +55,8 @@ class UpdateLogicFunctionFromSourceInputUpdates {
|
||||
toolInputSchema?: object;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/, {
|
||||
message: 'handlerName must be a valid JavaScript identifier',
|
||||
@Matches(HANDLER_NAME_REGEX, {
|
||||
message: 'handlerName must be a valid JavaScript identifier or dotted path',
|
||||
})
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
|
||||
+4
-9
@@ -1,4 +1,7 @@
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
STANDARD_OBJECTS,
|
||||
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS,
|
||||
} from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -20,14 +23,6 @@ import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-mi
|
||||
import { type UniversalFlatFieldMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-metadata.type';
|
||||
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
|
||||
|
||||
const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [
|
||||
'timelineActivity',
|
||||
'favorite',
|
||||
'attachment',
|
||||
'noteTarget',
|
||||
'taskTarget',
|
||||
] as const satisfies (keyof typeof STANDARD_OBJECTS)[];
|
||||
|
||||
const morphIdByRelationObjectNameSingular = {
|
||||
timelineActivity:
|
||||
STANDARD_OBJECTS.timelineActivity.morphIds.targetMorphId.morphId,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const API_CLIENT_DIR = 'api-client';
|
||||
@@ -1,10 +1,10 @@
|
||||
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
|
||||
import {
|
||||
type FieldMetadataDefaultValue,
|
||||
type FieldMetadataOptions,
|
||||
type FieldMetadataType,
|
||||
type FieldMetadataUniversalSettings,
|
||||
type RelationAndMorphRelationFieldMetadataType,
|
||||
FieldMetadataDefaultValue,
|
||||
FieldMetadataOptions,
|
||||
FieldMetadataType,
|
||||
FieldMetadataUniversalSettings,
|
||||
RelationAndMorphRelationFieldMetadataType
|
||||
} from '@/types';
|
||||
|
||||
export type RegularFieldManifest<
|
||||
@@ -25,20 +25,27 @@ export type RegularFieldManifest<
|
||||
objectUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
// Both sides of a relation must be declared explicitly in the manifest.
|
||||
// Relation-specific universal settings (relationType, onDelete, joinColumnName)
|
||||
// are provided through the `universalSettings` field.
|
||||
export type RelationFieldManifest<
|
||||
T extends
|
||||
RelationAndMorphRelationFieldMetadataType = RelationAndMorphRelationFieldMetadataType,
|
||||
> = RegularFieldManifest<T> & {
|
||||
> = Omit<RegularFieldManifest<T>, 'universalSettings' | 'type'> & {
|
||||
type: T;
|
||||
relationTargetFieldMetadataUniversalIdentifier: string;
|
||||
relationTargetObjectMetadataUniversalIdentifier: string;
|
||||
};
|
||||
universalSettings: FieldMetadataUniversalSettings<T>;
|
||||
} & ([T] extends [FieldMetadataType.MORPH_RELATION]
|
||||
? {
|
||||
morphId: string;
|
||||
}
|
||||
: {
|
||||
morphId?: undefined;
|
||||
});
|
||||
|
||||
export type FieldManifest<
|
||||
T extends FieldMetadataType = Exclude<
|
||||
FieldMetadataType,
|
||||
RelationAndMorphRelationFieldMetadataType
|
||||
>,
|
||||
> = RegularFieldManifest<T> | RelationFieldManifest;
|
||||
export type FieldManifest<T extends FieldMetadataType = FieldMetadataType> =
|
||||
T extends RelationAndMorphRelationFieldMetadataType
|
||||
? RelationFieldManifest<
|
||||
Extract<T, RelationAndMorphRelationFieldMetadataType>
|
||||
>
|
||||
: RegularFieldManifest<
|
||||
Exclude<T, RelationAndMorphRelationFieldMetadataType>
|
||||
>;
|
||||
|
||||
@@ -13,6 +13,7 @@ export type {
|
||||
} from './applicationType';
|
||||
export type { ApplicationVariables } from './applicationVariablesType';
|
||||
export type { AssetManifest } from './assetManifestType';
|
||||
export { API_CLIENT_DIR } from './constants/ApiClientDirectory';
|
||||
export { ASSETS_DIR } from './constants/AssetDirectory';
|
||||
export { DEFAULT_API_KEY_NAME } from './constants/DefaultApiKeyName';
|
||||
export { DEFAULT_API_URL_NAME } from './constants/DefaultApiUrlName';
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { type FieldManifest } from '@/application/fieldManifestType';
|
||||
import type { FieldMetadataType } from '@/types';
|
||||
|
||||
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
|
||||
? Omit<T, K>
|
||||
: never;
|
||||
|
||||
export type ObjectFieldManifest<
|
||||
T extends FieldMetadataType = Exclude<
|
||||
FieldMetadataType,
|
||||
FieldMetadataType.RELATION
|
||||
>,
|
||||
> = Omit<FieldManifest<T>, 'objectUniversalIdentifier'>;
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
> = DistributiveOmit<FieldManifest<T>, 'objectUniversalIdentifier'>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addCustomSuffixIfIsReserved } from '@/metadata/add-custom-suffix-if-reserved.util';
|
||||
import { addCustomSuffixIfIsReserved } from '@/metadata/utils/add-custom-suffix-if-reserved.util';
|
||||
|
||||
describe('addCustomSuffixIfIsReserved', () => {
|
||||
it('should add Custom suffix for reserved name "event"', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computeMetadataNameFromLabel } from '@/metadata/compute-metadata-name-from-label.util';
|
||||
import { computeMetadataNameFromLabel } from '@/metadata/utils/compute-metadata-name-from-label.util';
|
||||
|
||||
describe('computeMetadataNameFromLabel', () => {
|
||||
it('should convert a label to camelCase', () => {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { type ALL_METADATA_NAME } from '@/metadata/all-metadata-name.constant';
|
||||
|
||||
export type AllMetadataName = keyof typeof ALL_METADATA_NAME;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { type STANDARD_OBJECTS } from '@/metadata/constants/standard-object.constant';
|
||||
|
||||
export const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [
|
||||
'timelineActivity',
|
||||
'favorite',
|
||||
'attachment',
|
||||
'noteTarget',
|
||||
'taskTarget',
|
||||
] as const satisfies (keyof typeof STANDARD_OBJECTS)[];
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const CORE_OBJECT_NAMES = [
|
||||
const CORE_OBJECT_NAMES = [
|
||||
'approvedAccessDomain',
|
||||
'approvedAccessDomains',
|
||||
'appToken',
|
||||
@@ -7,23 +7,21 @@
|
||||
* |___/
|
||||
*/
|
||||
|
||||
export { addCustomSuffixIfIsReserved } from './add-custom-suffix-if-reserved.util';
|
||||
export { ALL_METADATA_NAME } from './all-metadata-name.constant';
|
||||
export type { AllMetadataName } from './all-metadata-name.type';
|
||||
export { checkIfFieldIsImageIdentifier } from './check-if-field-is-image-identifier.util';
|
||||
export {
|
||||
DEFAULT_LABEL_IDENTIFIER_FIELD_NAME,
|
||||
checkIfFieldIsLabelIdentifier,
|
||||
} from './check-if-field-is-label-identifier.util';
|
||||
export { computeMetadataNameFromLabel } from './compute-metadata-name-from-label.util';
|
||||
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 { RESERVED_METADATA_NAME_KEYWORDS } from './constants/reserved-metadata-name-keywords.constant';
|
||||
export { STANDARD_OBJECTS } from './constants/standard-object.constant';
|
||||
export type { AllMetadataName } from './types/all-metadata-name.type';
|
||||
export type {
|
||||
FailedMetadataValidationError,
|
||||
FailedMetadataValidation,
|
||||
MetadataValidationErrorResponse,
|
||||
} from './MetadataValidationError';
|
||||
export { WorkspaceMigrationV2ExceptionCode } from './MetadataValidationError';
|
||||
export {
|
||||
CORE_OBJECT_NAMES,
|
||||
RESERVED_METADATA_NAME_KEYWORDS,
|
||||
} from './reserved-metadata-name-keywords.constant';
|
||||
export { STANDARD_OBJECTS } from './standard-object.constant';
|
||||
} from './types/MetadataValidationError';
|
||||
export { WorkspaceMigrationV2ExceptionCode } from './types/MetadataValidationError';
|
||||
export { addCustomSuffixIfIsReserved } from './utils/add-custom-suffix-if-reserved.util';
|
||||
export { computeMetadataNameFromLabel } from './utils/compute-metadata-name-from-label.util';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { type AllMetadataName } from './all-metadata-name.type';
|
||||
import { type AllMetadataName } from '@/metadata/types/all-metadata-name.type';
|
||||
|
||||
export type FailedMetadataValidationError = {
|
||||
code: string;
|
||||
@@ -0,0 +1,3 @@
|
||||
import { type ALL_METADATA_NAME } from '@/metadata/constants/all-metadata-name.constant';
|
||||
|
||||
export type AllMetadataName = keyof typeof ALL_METADATA_NAME;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { RESERVED_METADATA_NAME_KEYWORDS } from './reserved-metadata-name-keywords.constant';
|
||||
import { RESERVED_METADATA_NAME_KEYWORDS } from '@/metadata';
|
||||
|
||||
export const addCustomSuffixIfIsReserved = (name: string): string => {
|
||||
if (!name) return name;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { addCustomSuffixIfIsReserved } from '@/metadata/add-custom-suffix-if-reserved.util';
|
||||
import { addCustomSuffixIfIsReserved } from '@/metadata/utils/add-custom-suffix-if-reserved.util';
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { slugify } from 'transliteration';
|
||||
|
||||
Reference in New Issue
Block a user