mirror of
https://github.com/twentyhq/twenty.git
synced 2026-06-12 09:57:03 -04:00
Add manifest integration tests (#18203)
- add integration test on sync manifest endpoint - fix invalid standard uuid + migration command
This commit is contained in:
@@ -0,0 +1,561 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { STANDARD_AGENT } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-agent.constant';
|
||||
import { STANDARD_ROLE } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-role.constant';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
const OLD_ROLE_ADMIN_UNIVERSAL_IDENTIFIER =
|
||||
'20202020-0001-0001-0001-000000000001';
|
||||
const OLD_AGENT_HELPER_UNIVERSAL_IDENTIFIER =
|
||||
'20202020-0002-0001-0001-000000000004';
|
||||
|
||||
const STANDARD_OBJECT_MISMATCHES = [
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'c9e5a0b4-1d36-4f8c-0a7b-3e4f5d6c7a81',
|
||||
newValue: '9d31ea73-13b6-4e06-84ee-c66c72bf7787',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'd0f6b1c5-2e47-4a9d-1b8c-4f5a6e7d8b92',
|
||||
newValue: '55637a5a-1edc-4351-8d76-d40020bf8944',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'e1a7c2d6-3f58-4b0e-2c9d-5a6b7f8e9c03',
|
||||
newValue: '4137ba06-184d-438f-b484-080f02a97659',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'f2b8d3e7-4a69-4c1f-3d0e-6b7c8a9f0d14',
|
||||
newValue: '8cc162d1-c127-4981-878d-f78622f8f12d',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '03c9e4f8-5b70-4d2a-4e1f-7c8d9b0a1e25',
|
||||
newValue: 'c10eba2d-ff1a-4eab-9285-50481c12a003',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '14d0f5a9-6c81-4e3b-5f2a-8d9e0c1b2f36',
|
||||
newValue: 'fadeab4b-79ee-4173-af79-72c51fbad888',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '25e1a6b0-7d92-4f4c-6a3b-9e0f1d2c3a47',
|
||||
newValue: '4daf320e-74d0-4f24-a45a-af3a09d741cb',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '36f2b7c1-8e03-4a5d-7b4c-0f1a2e3d4b58',
|
||||
newValue: 'ff6b86c1-3112-4dfa-b734-c4789111a716',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '69c5e0f4-1b36-4d8a-0e7f-3c4d5b6a7e81',
|
||||
newValue: 'c458ad97-8b95-43de-9003-88eb68576049',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '70d6f1a5-2c47-4e9b-1f8a-4d5e6c7b8f92',
|
||||
newValue: '30e9b75a-881f-4a85-aaf1-f2d2464be1cf',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '81e7a2b6-3d58-4f0c-2a9b-5e6f7d8c9003',
|
||||
newValue: '898aa202-428f-4a7a-a3b3-8f0a17a6658e',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '92f8b3c7-4e69-4a1d-3b0c-6f7a8e9d0114',
|
||||
newValue: 'ec2ebfc9-0c9b-4597-a87d-aa295e2d8bfe',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'a3a9c4d8-5f70-4b2e-4c1d-7a8b9f0e1225',
|
||||
newValue: 'dd300c61-f422-467a-91f4-de4f83c4175b',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'b4b0d5e9-6a81-4c3f-5d2e-8b9c0a1f2336',
|
||||
newValue: 'c3eb62df-2cc1-4cc3-b7aa-e96a4d65c633',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'c5c1e6f0-7b92-4d4a-6e3f-9c0d1b2a3447',
|
||||
newValue: '8e7ca28e-6002-4304-9dcc-0a8da93ca198',
|
||||
},
|
||||
{
|
||||
table: 'core."fieldMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '20202020-9b0c-5d6e-7f8a-9b0c1d2e3f4a',
|
||||
newValue: '99c330c0-5b7d-4276-a764-aed84499dfb5',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: 'd6d2f7a1-8c03-4e5b-7f4a-0d1e2c3b4558',
|
||||
newValue: 'e69f71aa-de0f-4b70-845f-7a8369c47928',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0905c0d4-1f36-4b8e-0c7d-3a4b5f6e788b',
|
||||
newValue: '1f360393-a336-435d-966d-8ec2645f875c',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1016d1e5-2a47-4c9f-1d8e-4b5c6a7f899c',
|
||||
newValue: 'c0369a13-49bd-48b0-a9f0-6ed0ee8e1b09',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2127e2f6-3b58-4d0a-2e9f-5c6d7b80900d',
|
||||
newValue: 'b8e9f696-5be4-48cb-815c-7c0bb8b69d38',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3238f3a7-4c69-4e1b-3f0a-6d7e8c91011e',
|
||||
newValue: 'd4f3ef9f-ae24-4cef-9d7a-684a24d4968b',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4349a4b8-5d70-4f2c-4a1b-7e8f9d02122f',
|
||||
newValue: 'a257158e-3a89-4715-970c-7fc38ac22370',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '5450b5c9-6e81-4a3d-5b2c-8f90ae132340',
|
||||
newValue: 'd55711a3-3297-4fef-beab-48d733712a33',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '6561c6d0-7f92-4b4e-6c3d-90a1bf243451',
|
||||
newValue: 'f30c9a75-a563-45a2-93b8-84f59004de8f',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '7672d7e1-8003-4c5f-7d4e-01b2c0354562',
|
||||
newValue: 'dca73027-adbe-4078-ae47-8d17850b9f2b',
|
||||
},
|
||||
{
|
||||
table: 'core."fieldMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '20202020-c3d4-e5f6-a7b8-901234567890',
|
||||
newValue: '9bfc9da7-ae2d-44fd-9563-ede90c5d6222',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0905a0b4-1336-4f8c-0a7b-34e5f3687895',
|
||||
newValue: '9bb24d40-60dd-4beb-8c64-a74e8c67f9ee',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1a16b1c5-2447-4a9d-1b8c-45f6a47989a6',
|
||||
newValue: '1b86ece8-7ce3-4df3-8771-fd4b5d45b2f2',
|
||||
},
|
||||
{
|
||||
table: 'core."fieldMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '20202020-d4e5-f6a7-b8c9-012345678901',
|
||||
newValue: '7411cfa3-4fd9-4b90-a636-940015fd7243',
|
||||
},
|
||||
{
|
||||
table: 'core."fieldMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '20202020-e5f6-a7b8-c9d0-123456789012',
|
||||
newValue: 'b3369d31-3856-4a7a-b007-ee353918127c',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2b38d3e7-5779-4c1f-4e0f-78c9d70d1de9',
|
||||
newValue: '8e6038aa-1f79-4a84-87b5-f33caa172e98',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3c49e4f8-6880-4d2a-5f1a-89d0e81e2ef0',
|
||||
newValue: '905299c3-ca81-435d-901c-f68b87562516',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4d50f5a9-7991-4e3b-6a2b-90e1f92f3f01',
|
||||
newValue: 'a3de1788-5dff-4849-ac5a-0dabe5fab216',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2b27c2d6-3558-4b0e-2c9d-56a7b58a9ab7',
|
||||
newValue: 'ab09a386-4dcc-41f7-8dc6-a6071e9c64b7',
|
||||
},
|
||||
{
|
||||
table: 'core."fieldMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '20202020-f6a7-b8c9-d0e1-234567890123',
|
||||
newValue: 'cce5ce1e-31d0-42e6-83cd-90059244a484',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3c38d3e7-4669-4c1f-3d0e-67b8c69b0bc8',
|
||||
newValue: '6217f2a5-28ac-4b88-8a2a-45eee4580e57',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4d49e4f8-5770-4d2a-4e1f-78c9d70c1cd9',
|
||||
newValue: 'ab0863ba-f95e-493c-b86c-56e1bc7e5bc2',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '5e50f5a9-6881-4e3b-5f2a-89d0e81d2de0',
|
||||
newValue: 'df805c2e-3bfe-4d51-8309-75e5eb4052fe',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '6f61a6b0-7992-4f4c-6a3b-90e1f92e3ef1',
|
||||
newValue: 'ce1e3a9e-afe9-439d-abb7-6cc98a6fa405',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '7072b7c1-8003-4a5d-7b4c-01f2a03f4f02',
|
||||
newValue: '7a05b45e-7aa6-4a7e-9bbc-299cbed53c96',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0305e0f4-1336-4d8a-0e7f-34c5d36c7c35',
|
||||
newValue: '7c069dc0-e83b-4cd5-aaa2-cac7f3e00d80',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1416f1a5-2447-4e9b-1f8a-45d6e47d8d46',
|
||||
newValue: '2d83909a-a383-4e82-b00a-8b7739f3f906',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2527a2b6-3558-4f0c-2a9b-56e7f58e9e57',
|
||||
newValue: '0d1a59b4-cc87-4b7d-804a-656e8504f371',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3638b3c7-4669-4a1d-3b0c-67f8a69f0f68',
|
||||
newValue: 'b8c2a673-a981-4357-a43d-313a358e4daa',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4749c4d8-5770-4b2e-4c1d-78a9b70a1a79',
|
||||
newValue: 'e161072d-37b1-477a-b944-ef0d65289574',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '5850d5e9-6881-4c3f-5d2e-89b0c81b2b80',
|
||||
newValue: 'ae60d580-b562-44f2-a24d-7b8040063f83',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '6961e6f0-7992-4d4a-6e3f-90c1d92c3c91',
|
||||
newValue: 'f53fdd28-a26b-47ba-81b5-6813ad622720',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '7072f7a1-8003-4e5b-7f4a-01d2e03d4d02',
|
||||
newValue: '8a265a5c-d3ae-47dc-bdf9-b42cfa2ba639',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0305c0d4-1336-4b8e-0c7d-34a5b36a7a35',
|
||||
newValue: 'f48fa3b1-0cec-44da-a9e5-f8a5e766637e',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1416d1e5-2447-4c9f-1d8e-45b6c47b8b46',
|
||||
newValue: 'a86b32b3-01d3-4302-a152-8b7f247db7b4',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2527e2f6-3558-4d0a-2e9f-56c7d58c9c57',
|
||||
newValue: 'c882f7a4-b025-4d32-aa26-5ef2595bdbf9',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3638f3a7-4669-4e1b-3f0a-67d8e69d0d68',
|
||||
newValue: 'b7d305d1-6fae-4ed6-9bdc-354fe9032c0e',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4749a4b8-5770-4f2c-4a1b-78e9f70e1e79',
|
||||
newValue: 'c0af54c7-751b-4bb2-b102-677cc4e47402',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '5850b5c9-6881-4a3d-5b2c-89f0a81f2f80',
|
||||
newValue: '6942e0ba-90f6-4c33-bf40-7f00b1ec35ab',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '6961c6d0-7992-4b4e-6c3d-90a1b92a3a91',
|
||||
newValue: '5e0b2391-85ca-4a66-aef4-52d74245bec2',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '7072d7e1-8003-4c5f-7d4e-01b2c03b4b02',
|
||||
newValue: '3e89a914-7bec-47bd-9cf9-743c6b83d001',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0305a0b4-1336-4f8c-0a7b-34e5f36e7e35',
|
||||
newValue: '995db1d8-0d3e-40f7-b0eb-5e6897bc9966',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1416b1c5-2447-4a9d-1b8c-45f6a47f8f46',
|
||||
newValue: '609cf622-86ef-48d1-812b-e1cab610a46c',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2527c2d6-3558-4b0e-2c9d-56a7b58a9a57',
|
||||
newValue: 'd6059ec2-92b0-4cfc-9fd8-78050f03108f',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3638d3e7-4669-4c1f-3d0e-67b8c69b0b68',
|
||||
newValue: 'd94329b3-5dc8-4141-ae28-31afe28f7135',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4749e4f8-5770-4d2a-4e1f-78c9d70c1c79',
|
||||
newValue: '1a2bd046-7c23-4e0a-9f8a-c3ca3a16d3b9',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '5850f5a9-6881-4e3b-5f2a-89d0e81d2d80',
|
||||
newValue: 'e8821da9-728d-470a-bf5b-5a981fff7880',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '6961a6b0-7992-4f4c-6a3b-90e1f92e3e91',
|
||||
newValue: 'c7e64c55-eb0c-4b93-b076-5cfcf2e2e042',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '7072b7c1-8003-4a5d-7b4c-01f2a03f4f03',
|
||||
newValue: '7331ff89-a3f9-4ac0-9fa9-0de5663ae7b2',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '0305e0f4-1336-4d8a-0e7f-34c5d36c7c36',
|
||||
newValue: 'e0ac5ad2-d0c8-4f72-b710-8e53b9dc18d9',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '1416f1a5-2447-4e9b-1f8a-45d6e47d8d47',
|
||||
newValue: '8138c3b3-0b14-4ee1-be0e-debdde6b3219',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '2527a2b6-3558-4f0c-2a9b-56e7f58e9e58',
|
||||
newValue: '6f3a65eb-2aee-4108-b8a0-c62da419d1dc',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '3638b3c7-4669-4a1d-3b0c-67f8a69f0f69',
|
||||
newValue: '76da5f27-523c-44b6-ad06-12954f6b949f',
|
||||
},
|
||||
{
|
||||
table: 'core."indexMetadata"',
|
||||
column: '"universalIdentifier"',
|
||||
oldValue: '4749c4d8-5770-4b2e-4c1d-78a9b70a1a7a',
|
||||
newValue: '8678dde9-a804-4a9e-80e3-9af35e471ec5',
|
||||
},
|
||||
];
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-19:fix-invalid-standard-universal-identifiers',
|
||||
description:
|
||||
'Fix invalid universalIdentifier values in core tables to comply with UUID v4 format',
|
||||
})
|
||||
export class FixInvalidStandardUniversalIdentifiersCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options?.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Fixing universal identifiers for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would fix role, agent, and ${STANDARD_OBJECT_MISMATCHES.length} standard object universal identifier mismatches in workspace ${workspaceId}. Skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
|
||||
try {
|
||||
let totalUpdated = 0;
|
||||
|
||||
const roleResult = await queryRunner.query(
|
||||
`UPDATE core."role"
|
||||
SET "universalIdentifier" = $1
|
||||
WHERE "workspaceId" = $2
|
||||
AND "universalIdentifier" = $3`,
|
||||
[
|
||||
STANDARD_ROLE.admin.universalIdentifier,
|
||||
workspaceId,
|
||||
OLD_ROLE_ADMIN_UNIVERSAL_IDENTIFIER,
|
||||
],
|
||||
);
|
||||
|
||||
const roleUpdatedCount = roleResult?.[1] ?? 0;
|
||||
|
||||
if (roleUpdatedCount > 0) {
|
||||
this.logger.log(
|
||||
`Updated ${roleUpdatedCount} role universalIdentifier in workspace ${workspaceId}`,
|
||||
);
|
||||
totalUpdated += roleUpdatedCount;
|
||||
}
|
||||
|
||||
const agentResult = await queryRunner.query(
|
||||
`UPDATE core."agent"
|
||||
SET "universalIdentifier" = $1
|
||||
WHERE "workspaceId" = $2
|
||||
AND "universalIdentifier" = $3`,
|
||||
[
|
||||
STANDARD_AGENT.helper.universalIdentifier,
|
||||
workspaceId,
|
||||
OLD_AGENT_HELPER_UNIVERSAL_IDENTIFIER,
|
||||
],
|
||||
);
|
||||
|
||||
const agentUpdatedCount = agentResult?.[1] ?? 0;
|
||||
|
||||
if (agentUpdatedCount > 0) {
|
||||
this.logger.log(
|
||||
`Updated ${agentUpdatedCount} agent universalIdentifier in workspace ${workspaceId}`,
|
||||
);
|
||||
totalUpdated += agentUpdatedCount;
|
||||
}
|
||||
|
||||
const updateCountsByTable: Record<string, number> = {};
|
||||
|
||||
for (const mismatch of STANDARD_OBJECT_MISMATCHES) {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE ${mismatch.table}
|
||||
SET ${mismatch.column} = $1
|
||||
WHERE "workspaceId" = $2
|
||||
AND ${mismatch.column} = $3`,
|
||||
[mismatch.newValue, workspaceId, mismatch.oldValue],
|
||||
);
|
||||
|
||||
const updatedCount = result?.[1] ?? 0;
|
||||
|
||||
if (updatedCount > 0) {
|
||||
const tableKey = `${mismatch.table}.${mismatch.column}`;
|
||||
|
||||
updateCountsByTable[tableKey] =
|
||||
(updateCountsByTable[tableKey] ?? 0) + updatedCount;
|
||||
totalUpdated += updatedCount;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [tableKey, count] of Object.entries(updateCountsByTable)) {
|
||||
this.logger.log(
|
||||
`Updated ${count} rows in ${tableKey} for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (totalUpdated === 0) {
|
||||
this.logger.log(
|
||||
`No universal identifiers needed updating in workspace ${workspaceId}`,
|
||||
);
|
||||
} else {
|
||||
this.logger.log(
|
||||
`Updated ${totalUpdated} total universal identifiers in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { AddMissingSystemFieldsToStandardObjectsCommand } from 'src/database/com
|
||||
import { BackfillMessageChannelMessageAssociationMessageFolderCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-page-layouts.command';
|
||||
import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-system-fields-is-system.command';
|
||||
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -32,12 +33,14 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
AddMissingSystemFieldsToStandardObjectsCommand,
|
||||
BackfillMessageChannelMessageAssociationMessageFolderCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
FixInvalidStandardUniversalIdentifiersCommand,
|
||||
],
|
||||
exports: [
|
||||
BackfillSystemFieldsIsSystemCommand,
|
||||
AddMissingSystemFieldsToStandardObjectsCommand,
|
||||
BackfillMessageChannelMessageAssociationMessageFolderCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
FixInvalidStandardUniversalIdentifiersCommand,
|
||||
],
|
||||
})
|
||||
export class V1_19_UpgradeVersionCommandModule {}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { AddMissingSystemFieldsToStandardObjectsCommand } from 'src/database/com
|
||||
import { BackfillMessageChannelMessageAssociationMessageFolderCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-page-layouts.command';
|
||||
import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-system-fields-is-system.command';
|
||||
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
@@ -75,6 +76,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly addMissingSystemFieldsToStandardObjectsCommand: AddMissingSystemFieldsToStandardObjectsCommand,
|
||||
protected readonly backfillMessageChannelMessageAssociationMessageFolderCommand: BackfillMessageChannelMessageAssociationMessageFolderCommand,
|
||||
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
|
||||
protected readonly fixRoleAndAgentUniversalIdentifiersCommand: FixInvalidStandardUniversalIdentifiersCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -115,6 +117,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.addMissingSystemFieldsToStandardObjectsCommand,
|
||||
this.backfillMessageChannelMessageAssociationMessageFolderCommand,
|
||||
this.backfillPageLayoutsCommand,
|
||||
this.fixRoleAndAgentUniversalIdentifiersCommand,
|
||||
];
|
||||
|
||||
this.allCommands = {
|
||||
|
||||
@@ -9,7 +9,7 @@ const checkUUID = (value: any): string => {
|
||||
throw new ValidationError('UUID must be a string');
|
||||
}
|
||||
if (!uuidValidate(value)) {
|
||||
throw new ValidationError(`Invalid UUID`, {
|
||||
throw new ValidationError(`Invalid UUID: '${value}'`, {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({
|
||||
isSearchable: false,
|
||||
duplicateCriteria: null,
|
||||
shortcut: null,
|
||||
isLabelSyncedWithName: true,
|
||||
isLabelSyncedWithName: false,
|
||||
fieldUniversalIdentifiers: [],
|
||||
indexMetadataUniversalIdentifiers: [],
|
||||
viewUniversalIdentifiers: [],
|
||||
|
||||
@@ -18,6 +18,7 @@ export class CreateFieldInput extends OmitType(
|
||||
'standardOverrides',
|
||||
'applicationId',
|
||||
'morphId',
|
||||
'universalIdentifier',
|
||||
] as const,
|
||||
InputType,
|
||||
) {
|
||||
|
||||
@@ -64,6 +64,11 @@ export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@IDField(() => UUIDScalarType)
|
||||
universalIdentifier: string;
|
||||
|
||||
@IsEnum(FieldMetadataType)
|
||||
@IsNotEmpty()
|
||||
@Field(() => FieldMetadataType)
|
||||
|
||||
@@ -14,6 +14,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (
|
||||
isUnique,
|
||||
settings,
|
||||
id,
|
||||
universalIdentifier,
|
||||
label,
|
||||
name,
|
||||
objectMetadataId,
|
||||
@@ -32,6 +33,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (
|
||||
|
||||
return {
|
||||
id,
|
||||
universalIdentifier,
|
||||
label,
|
||||
name,
|
||||
objectMetadataId,
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { areFlatObjectMetadataNamesSyncedWithLabels } from 'src/engine/metadata-modules/flat-object-metadata/utils/are-flat-object-metadata-names-synced-with-labels.util';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
|
||||
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type';
|
||||
|
||||
const THIRD_PARTY_BUILD_OPTIONS: WorkspaceMigrationBuilderOptions = {
|
||||
isSystemBuild: false,
|
||||
applicationUniversalIdentifier: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||
};
|
||||
|
||||
const TWENTY_STANDARD_BUILD_OPTIONS: WorkspaceMigrationBuilderOptions = {
|
||||
isSystemBuild: false,
|
||||
applicationUniversalIdentifier:
|
||||
TWENTY_STANDARD_APPLICATION.universalIdentifier,
|
||||
};
|
||||
|
||||
describe('areFlatObjectMetadataNamesSyncedWithLabels', () => {
|
||||
it('should return true when names match computed names from labels', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when singular name does not match', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'wrongName',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when plural name does not match', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'wrongPlural',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should apply custom suffix for reserved words when caller is a third-party app', () => {
|
||||
// "Event" computes to "event" which is reserved, so suffix "Custom" is appended
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'eventCustom',
|
||||
namePlural: 'eventsCustom',
|
||||
labelSingular: 'Event',
|
||||
labelPlural: 'Events',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for reserved words without custom suffix when caller is a third-party app', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'event',
|
||||
namePlural: 'events',
|
||||
labelSingular: 'Event',
|
||||
labelPlural: 'Events',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should not apply custom suffix for reserved words when caller is the Twenty Standard app', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'event',
|
||||
namePlural: 'events',
|
||||
labelSingular: 'Event',
|
||||
labelPlural: 'Events',
|
||||
},
|
||||
buildOptions: TWENTY_STANDARD_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle multi-word labels by computing camelCase names', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'supportTicket',
|
||||
namePlural: 'supportTickets',
|
||||
labelSingular: 'Support Ticket',
|
||||
labelPlural: 'Support Tickets',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when both names do not match', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'foo',
|
||||
namePlural: 'bar',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true with complex labels', () => {
|
||||
const result = areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectMetadata: {
|
||||
nameSingular: 'wrongCreatedAtObject',
|
||||
namePlural: 'wrongCreatedAtObjects',
|
||||
labelSingular: 'Wrong CreatedAt Object',
|
||||
labelPlural: 'Wrong CreatedAt Objects',
|
||||
},
|
||||
buildOptions: THIRD_PARTY_BUILD_OPTIONS,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -5,18 +5,18 @@ import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/w
|
||||
import { type WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-builder-options.type';
|
||||
|
||||
export const areFlatObjectMetadataNamesSyncedWithLabels = ({
|
||||
flatObjectdMetadata,
|
||||
flatObjectMetadata,
|
||||
buildOptions,
|
||||
}: {
|
||||
buildOptions: WorkspaceMigrationBuilderOptions;
|
||||
flatObjectdMetadata: Pick<
|
||||
flatObjectMetadata: Pick<
|
||||
UniversalFlatObjectMetadata,
|
||||
'namePlural' | 'nameSingular' | 'labelPlural' | 'labelSingular'
|
||||
>;
|
||||
}) => {
|
||||
const [computedSingularName, computedPluralName] = [
|
||||
flatObjectdMetadata.labelSingular,
|
||||
flatObjectdMetadata.labelPlural,
|
||||
flatObjectMetadata.labelSingular,
|
||||
flatObjectMetadata.labelPlural,
|
||||
].map((label) =>
|
||||
computeMetadataNameFromLabel({
|
||||
label,
|
||||
@@ -25,7 +25,7 @@ export const areFlatObjectMetadataNamesSyncedWithLabels = ({
|
||||
);
|
||||
|
||||
return (
|
||||
flatObjectdMetadata.nameSingular === computedSingularName &&
|
||||
flatObjectdMetadata.namePlural === computedPluralName
|
||||
flatObjectMetadata.nameSingular === computedSingularName &&
|
||||
flatObjectMetadata.namePlural === computedPluralName
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
|
||||
shortcut,
|
||||
duplicateCriteria,
|
||||
id,
|
||||
universalIdentifier,
|
||||
isActive,
|
||||
isCustom,
|
||||
isLabelSyncedWithName,
|
||||
@@ -32,6 +33,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
|
||||
|
||||
return {
|
||||
id,
|
||||
universalIdentifier,
|
||||
isActive,
|
||||
isCustom,
|
||||
isLabelSyncedWithName,
|
||||
|
||||
@@ -66,7 +66,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({
|
||||
if (
|
||||
universalFlatObjectMetadataToValidate.isLabelSyncedWithName &&
|
||||
!areFlatObjectMetadataNamesSyncedWithLabels({
|
||||
flatObjectdMetadata: universalFlatObjectMetadataToValidate,
|
||||
flatObjectMetadata: universalFlatObjectMetadataToValidate,
|
||||
buildOptions,
|
||||
})
|
||||
) {
|
||||
|
||||
@@ -32,6 +32,9 @@ export class ObjectMetadataDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@IDField(() => UUIDScalarType)
|
||||
universalIdentifier: string;
|
||||
|
||||
@Field()
|
||||
nameSingular: string;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => {
|
||||
return {
|
||||
id: role.id,
|
||||
label: role.label,
|
||||
universalIdentifier: role.universalIdentifier,
|
||||
canUpdateAllSettings: role.canUpdateAllSettings,
|
||||
canAccessAllTools: role.canAccessAllTools,
|
||||
description: role.description ?? undefined,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const STANDARD_AGENT = {
|
||||
helper: {
|
||||
universalIdentifier: '20202020-0002-0001-0001-000000000004',
|
||||
universalIdentifier: '20202020-c7ab-4065-b822-0ca1d5de60a9',
|
||||
},
|
||||
} as const satisfies Record<
|
||||
string,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const STANDARD_ROLE = {
|
||||
admin: { universalIdentifier: '20202020-0001-0001-0001-000000000001' },
|
||||
admin: { universalIdentifier: '20202020-02c2-43f2-b94d-cab1f2b532eb' },
|
||||
} as const satisfies Record<string, { universalIdentifier: string }>;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
import { type WorkspaceMigrationActionType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common';
|
||||
import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
|
||||
|
||||
export type FlatEntityValidationError<TCode extends string = string> = {
|
||||
code: TCode;
|
||||
@@ -13,9 +13,9 @@ export type FlatEntityValidationError<TCode extends string = string> = {
|
||||
|
||||
export type FailedFlatEntityValidation<
|
||||
TMetadataName extends AllMetadataName,
|
||||
TAcionType extends WorkspaceMigrationActionType,
|
||||
TActionType extends WorkspaceMigrationActionType,
|
||||
> = {
|
||||
type: TAcionType;
|
||||
type: TActionType;
|
||||
metadataName: TMetadataName;
|
||||
errors: FlatEntityValidationError[];
|
||||
flatEntityMinimalInformation: Partial<MetadataFlatEntity<TMetadataName>>;
|
||||
|
||||
@@ -79,80 +79,6 @@ exports[`Sync application should fail due to object system fields integrity shou
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail due to object system fields integrity when object has createdAt field with wrong type (TEXT instead of DATE_TIME) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"fieldMetadata": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "OBJECT_METADATA_NOT_FOUND",
|
||||
"message": "Object metadata not found",
|
||||
"userFriendlyMessage": "Field to create related object not found",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "title",
|
||||
"objectMetadataUniversalIdentifier": Any<String>,
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "fieldMetadata",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "OBJECT_METADATA_NOT_FOUND",
|
||||
"message": "Object metadata not found",
|
||||
"userFriendlyMessage": "Field to create related object not found",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"name": "createdAt",
|
||||
"objectMetadataUniversalIdentifier": Any<String>,
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "fieldMetadata",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
"objectMetadata": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "INVALID_OBJECT_INPUT",
|
||||
"message": "Names are not synced with labels",
|
||||
"userFriendlyMessage": "Names are not synced with labels",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"namePlural": "wrongCreatedAtObjects",
|
||||
"nameSingular": "wrongCreatedAtObject",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "objectMetadata",
|
||||
"status": "fail",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 2 fieldMetadatas, 1 objectMetadata",
|
||||
"summary": {
|
||||
"fieldMetadata": 2,
|
||||
"objectMetadata": 1,
|
||||
"totalErrors": 3,
|
||||
},
|
||||
"userFriendlyMessage": "Metadata validation failed",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail due to object system fields integrity when object has id field with wrong type (TEXT instead of UUID) 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
@@ -389,3 +315,82 @@ exports[`Sync application should fail due to object system fields integrity when
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Sync application should fail due to object system fields integrity when object miss default fields 1`] = `
|
||||
{
|
||||
"extensions": {
|
||||
"code": "METADATA_VALIDATION_FAILED",
|
||||
"errors": {
|
||||
"objectMetadata": [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field id is missing",
|
||||
"userFriendlyMessage": "System field id is missing",
|
||||
"value": "id",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field createdAt is missing",
|
||||
"userFriendlyMessage": "System field createdAt is missing",
|
||||
"value": "createdAt",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field updatedAt is missing",
|
||||
"userFriendlyMessage": "System field updatedAt is missing",
|
||||
"value": "updatedAt",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field deletedAt is missing",
|
||||
"userFriendlyMessage": "System field deletedAt is missing",
|
||||
"value": "deletedAt",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field createdBy is missing",
|
||||
"userFriendlyMessage": "System field createdBy is missing",
|
||||
"value": "createdBy",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field updatedBy is missing",
|
||||
"userFriendlyMessage": "System field updatedBy is missing",
|
||||
"value": "updatedBy",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field position is missing",
|
||||
"userFriendlyMessage": "System field position is missing",
|
||||
"value": "position",
|
||||
},
|
||||
{
|
||||
"code": "MISSING_SYSTEM_FIELD",
|
||||
"message": "System field searchVector is missing",
|
||||
"userFriendlyMessage": "System field searchVector is missing",
|
||||
"value": "searchVector",
|
||||
},
|
||||
],
|
||||
"flatEntityMinimalInformation": {
|
||||
"namePlural": "wrongCreatedAtObjects",
|
||||
"nameSingular": "wrongCreatedAtObject",
|
||||
"universalIdentifier": Any<String>,
|
||||
},
|
||||
"metadataName": "objectMetadata",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "Validation failed for 1 objectMetadata",
|
||||
"summary": {
|
||||
"objectMetadata": 1,
|
||||
"totalErrors": 1,
|
||||
},
|
||||
"userFriendlyMessage": "Metadata validation failed",
|
||||
},
|
||||
"message": "Validation errors occurred while syncing application manifest metadata",
|
||||
"name": "GraphQLError",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -78,7 +78,7 @@ exports[`syncApplication should return workspace migration actions on initial sy
|
||||
"isActive": true,
|
||||
"isAuditLogged": true,
|
||||
"isCustom": true,
|
||||
"isLabelSyncedWithName": true,
|
||||
"isLabelSyncedWithName": false,
|
||||
"isRemote": false,
|
||||
"isSearchable": false,
|
||||
"isSystem": false,
|
||||
@@ -364,81 +364,6 @@ exports[`syncApplication should return workspace migration actions on initial sy
|
||||
"metadataName": "role",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`syncApplication should return workspace migration actions on initial sync then on second sync with field rename and new role 2`] = `
|
||||
{
|
||||
"syncApplication": {
|
||||
"actions": [
|
||||
{
|
||||
"metadataName": "fieldMetadata",
|
||||
"type": "update",
|
||||
"universalIdentifier": Any<String>,
|
||||
"update": {
|
||||
"label": "Body",
|
||||
"name": "body",
|
||||
},
|
||||
},
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"canAccessAllTools": false,
|
||||
"canBeAssignedToAgents": true,
|
||||
"canBeAssignedToApiKeys": true,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"createdAt": Any<String>,
|
||||
"description": "A read-only role",
|
||||
"icon": null,
|
||||
"isEditable": true,
|
||||
"label": "Viewer Role",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
"metadataName": "role",
|
||||
"type": "create",
|
||||
},
|
||||
],
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`syncApplication should sync a skill then update it on second sync 1`] = `
|
||||
{
|
||||
"syncApplication": {
|
||||
"actions": [
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
"canAccessAllTools": false,
|
||||
"canBeAssignedToAgents": true,
|
||||
"canBeAssignedToApiKeys": true,
|
||||
"canBeAssignedToUsers": true,
|
||||
"canDestroyAllObjectRecords": false,
|
||||
"canReadAllObjectRecords": false,
|
||||
"canSoftDeleteAllObjectRecords": false,
|
||||
"canUpdateAllObjectRecords": false,
|
||||
"canUpdateAllSettings": false,
|
||||
"createdAt": Any<String>,
|
||||
"description": "A test role",
|
||||
"icon": null,
|
||||
"isEditable": true,
|
||||
"label": "Test Role",
|
||||
"universalIdentifier": Any<String>,
|
||||
"updatedAt": Any<String>,
|
||||
},
|
||||
"metadataName": "role",
|
||||
"type": "create",
|
||||
},
|
||||
{
|
||||
"flatEntity": {
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
@@ -463,25 +388,3 @@ This is a test skill.",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`syncApplication should sync a skill then update it on second sync 2`] = `
|
||||
{
|
||||
"syncApplication": {
|
||||
"actions": [
|
||||
{
|
||||
"metadataName": "skill",
|
||||
"type": "update",
|
||||
"universalIdentifier": Any<String>,
|
||||
"update": {
|
||||
"content": "# Test Skill
|
||||
|
||||
This is an updated test skill with more content.",
|
||||
"description": "An updated skill for testing",
|
||||
"label": "Test Skill Updated",
|
||||
},
|
||||
},
|
||||
],
|
||||
"applicationUniversalIdentifier": Any<String>,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util';
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/application/utils/build-default-object-manifest.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
@@ -20,37 +21,12 @@ type TestContext = {
|
||||
|
||||
type SyncApplicationTestingContext = EachTestingContext<TestContext>[];
|
||||
|
||||
const buildBaseManifest = (
|
||||
overrides: Pick<Manifest, 'objects' | 'fields'>,
|
||||
): Manifest => ({
|
||||
application: {
|
||||
apiClientChecksum: '',
|
||||
marketplaceData: undefined,
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
defaultRoleUniversalIdentifier: TEST_ROLE_ID,
|
||||
displayName: 'Test System Fields App',
|
||||
description: 'App for testing system field validation',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
views: [],
|
||||
navigationMenuItems: [],
|
||||
pageLayouts: [],
|
||||
skills: [],
|
||||
...overrides,
|
||||
});
|
||||
const buildManifest = (overrides: Pick<Manifest, 'objects' | 'fields'>) =>
|
||||
buildBaseManifest({
|
||||
appId: TEST_APP_ID,
|
||||
roleId: TEST_ROLE_ID,
|
||||
overrides,
|
||||
});
|
||||
|
||||
const buildObjectWithLabelField = ({
|
||||
nameSingular,
|
||||
@@ -104,7 +80,7 @@ const failingSyncApplicationSystemFieldsTestCases: SyncApplicationTestingContext
|
||||
title:
|
||||
'when object is created without any system fields (missing all 8 system fields)',
|
||||
context: {
|
||||
manifest: buildBaseManifest(
|
||||
manifest: buildManifest(
|
||||
buildObjectWithLabelField({
|
||||
nameSingular: 'noSystemFieldsObject',
|
||||
namePlural: 'noSystemFieldsObjects',
|
||||
@@ -118,7 +94,7 @@ const failingSyncApplicationSystemFieldsTestCases: SyncApplicationTestingContext
|
||||
{
|
||||
title: 'when object has id field with wrong type (TEXT instead of UUID)',
|
||||
context: {
|
||||
manifest: buildBaseManifest(
|
||||
manifest: buildManifest(
|
||||
buildObjectWithLabelField({
|
||||
nameSingular: 'wrongIdTypeObject',
|
||||
namePlural: 'wrongIdTypeObjects',
|
||||
@@ -140,26 +116,15 @@ const failingSyncApplicationSystemFieldsTestCases: SyncApplicationTestingContext
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'when object has createdAt field with wrong type (TEXT instead of DATE_TIME)',
|
||||
title: 'when object miss default fields',
|
||||
context: {
|
||||
manifest: buildBaseManifest(
|
||||
manifest: buildManifest(
|
||||
buildObjectWithLabelField({
|
||||
nameSingular: 'wrongCreatedAtObject',
|
||||
namePlural: 'wrongCreatedAtObjects',
|
||||
labelSingular: 'Wrong CreatedAt Object',
|
||||
labelPlural: 'Wrong CreatedAt Objects',
|
||||
description: 'Object with wrong createdAt field type',
|
||||
additionalFields: [
|
||||
{
|
||||
universalIdentifier: uuidv4(),
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'createdAt',
|
||||
label: 'Created At',
|
||||
description: 'Created at field with wrong type',
|
||||
icon: 'IconCalendar',
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -168,7 +133,7 @@ const failingSyncApplicationSystemFieldsTestCases: SyncApplicationTestingContext
|
||||
title:
|
||||
'when object has position field with wrong type (TEXT instead of POSITION)',
|
||||
context: {
|
||||
manifest: buildBaseManifest(
|
||||
manifest: buildManifest(
|
||||
buildObjectWithLabelField({
|
||||
nameSingular: 'wrongPositionObject',
|
||||
namePlural: 'wrongPositionObjects',
|
||||
@@ -240,7 +205,7 @@ describe('Sync application should fail due to object system fields integrity', (
|
||||
description: 'Object for testing system field deletion',
|
||||
});
|
||||
|
||||
const validManifest = buildBaseManifest({
|
||||
const validManifest = buildManifest({
|
||||
objects: [testObject],
|
||||
fields: [],
|
||||
});
|
||||
@@ -250,7 +215,7 @@ describe('Sync application should fail due to object system fields integrity', (
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const manifestWithDeletedIdField = buildBaseManifest({
|
||||
const manifestWithDeletedIdField = buildManifest({
|
||||
objects: [
|
||||
{
|
||||
...testObject,
|
||||
@@ -277,7 +242,7 @@ describe('Sync application should fail due to object system fields integrity', (
|
||||
description: 'Object for testing system field update',
|
||||
});
|
||||
|
||||
const validManifest = buildBaseManifest({
|
||||
const validManifest = buildManifest({
|
||||
objects: [testObject],
|
||||
fields: [],
|
||||
});
|
||||
@@ -287,7 +252,7 @@ describe('Sync application should fail due to object system fields integrity', (
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const manifestWithUpdatedIdField = buildBaseManifest({
|
||||
const manifestWithUpdatedIdField = buildManifest({
|
||||
objects: [
|
||||
{
|
||||
...testObject,
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/application/utils/build-default-object-manifest.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
const TEST_FIELD_ID = uuidv4();
|
||||
const TEST_SECOND_FIELD_ID = uuidv4();
|
||||
|
||||
const TEST_OBJECT = buildDefaultObjectManifest({
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
const buildManifest = (overrides?: Partial<Pick<Manifest, 'fields'>>) =>
|
||||
buildBaseManifest({
|
||||
appId: TEST_APP_ID,
|
||||
roleId: TEST_ROLE_ID,
|
||||
overrides: { objects: [TEST_OBJECT], ...overrides },
|
||||
});
|
||||
|
||||
const findObjectFields = async () => {
|
||||
const objects = await findManyObjectMetadataWithIndexes({
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const object = objects.find(
|
||||
(o) => o.universalIdentifier === TEST_OBJECT.universalIdentifier,
|
||||
);
|
||||
|
||||
return object?.fieldsList ?? [];
|
||||
};
|
||||
|
||||
describe('Manifest update - fields', () => {
|
||||
beforeEach(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Application',
|
||||
description: 'App for testing field manifest updates',
|
||||
sourcePath: 'test-manifest-update-field',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterEach(async () => {
|
||||
await uninstallApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new field when added to manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ fields: [] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterFirstSync = await findObjectFields();
|
||||
|
||||
const descriptionBefore = fieldsAfterFirstSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
);
|
||||
|
||||
expect(descriptionBefore).toBeUndefined();
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterSecondSync = await findObjectFields();
|
||||
const descriptionAfter = fieldsAfterSecondSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
);
|
||||
|
||||
expect(descriptionAfter).toBeDefined();
|
||||
expect(descriptionAfter).toMatchObject({
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: FieldMetadataType.TEXT,
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should update a field when properties change in manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterFirstSync = await findObjectFields();
|
||||
const fieldBefore = fieldsAfterFirstSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
);
|
||||
|
||||
expect(fieldBefore).toBeDefined();
|
||||
expect(fieldBefore).toMatchObject({
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
description: 'Ticket body content',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterSecondSync = await findObjectFields();
|
||||
const renamedField = fieldsAfterSecondSync.find(
|
||||
(field: { name: string }) => field.name === 'body',
|
||||
);
|
||||
|
||||
expect(renamedField).toBeDefined();
|
||||
expect(renamedField).toMatchObject({
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
type: FieldMetadataType.TEXT,
|
||||
description: 'Ticket body content',
|
||||
});
|
||||
|
||||
const oldField = fieldsAfterSecondSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
);
|
||||
|
||||
expect(oldField).toBeUndefined();
|
||||
}, 60000);
|
||||
|
||||
it('should delete a field when removed from manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'priority',
|
||||
label: 'Priority',
|
||||
description: 'Ticket priority',
|
||||
icon: 'IconFlag',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterFirstSync = await findObjectFields();
|
||||
|
||||
expect(
|
||||
fieldsAfterFirstSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
fieldsAfterFirstSync.find(
|
||||
(field: { name: string }) => field.name === 'priority',
|
||||
),
|
||||
).toBeDefined();
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const fieldsAfterSecondSync = await findObjectFields();
|
||||
|
||||
expect(
|
||||
fieldsAfterSecondSync.find(
|
||||
(field: { name: string }) => field.name === 'description',
|
||||
),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
fieldsAfterSecondSync.find(
|
||||
(field: { name: string }) => field.name === 'priority',
|
||||
),
|
||||
).toBeUndefined();
|
||||
}, 60000);
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/application/utils/build-default-object-manifest.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
|
||||
const buildManifest = (
|
||||
overrides?: Partial<Pick<Manifest, 'objects' | 'fields'>>,
|
||||
) => buildBaseManifest({ appId: TEST_APP_ID, roleId: TEST_ROLE_ID, overrides });
|
||||
|
||||
const OBJECT_GQL_FIELDS =
|
||||
'id nameSingular namePlural labelSingular labelPlural description icon isCustom isActive';
|
||||
|
||||
const findCustomObjects = async () => {
|
||||
const { objects } = await findManyObjectMetadata({
|
||||
input: {
|
||||
filter: { isCustom: { is: true } },
|
||||
paging: { first: 100 },
|
||||
},
|
||||
gqlFields: OBJECT_GQL_FIELDS,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
return objects;
|
||||
};
|
||||
|
||||
describe('Manifest update - objects', () => {
|
||||
beforeEach(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Application',
|
||||
description: 'App for testing object manifest updates',
|
||||
sourcePath: 'test-manifest-update-object',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterEach(async () => {
|
||||
await uninstallApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new object when added to manifest on second sync', async () => {
|
||||
const ticketObject = buildDefaultObjectManifest({
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ objects: [ticketObject] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterFirstSync = await findCustomObjects();
|
||||
const ticket = objectsAfterFirstSync.find(
|
||||
(obj) => obj.nameSingular === 'ticket',
|
||||
);
|
||||
|
||||
expect(ticket).toBeDefined();
|
||||
expect(ticket).toMatchObject({
|
||||
nameSingular: 'ticket',
|
||||
labelSingular: 'Ticket',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
const invoiceObject = buildDefaultObjectManifest({
|
||||
nameSingular: 'invoice',
|
||||
namePlural: 'invoices',
|
||||
labelSingular: 'Invoice',
|
||||
labelPlural: 'Invoices',
|
||||
description: 'A billing invoice',
|
||||
icon: 'IconFileInvoice',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
objects: [ticketObject, invoiceObject],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterSecondSync = await findCustomObjects();
|
||||
const ticketAfter = objectsAfterSecondSync.find(
|
||||
(obj) => obj.nameSingular === 'ticket',
|
||||
);
|
||||
const invoiceAfter = objectsAfterSecondSync.find(
|
||||
(obj) => obj.nameSingular === 'invoice',
|
||||
);
|
||||
|
||||
expect(ticketAfter).toBeDefined();
|
||||
expect(invoiceAfter).toBeDefined();
|
||||
expect(invoiceAfter).toMatchObject({
|
||||
nameSingular: 'invoice',
|
||||
namePlural: 'invoices',
|
||||
labelSingular: 'Invoice',
|
||||
labelPlural: 'Invoices',
|
||||
description: 'A billing invoice',
|
||||
icon: 'IconFileInvoice',
|
||||
isCustom: true,
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should update object properties when changed in manifest on second sync', async () => {
|
||||
const ticketObject = buildDefaultObjectManifest({
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ objects: [ticketObject] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterFirstSync = await findCustomObjects();
|
||||
const ticketBefore = objectsAfterFirstSync.find(
|
||||
(obj) => obj.nameSingular === 'ticket',
|
||||
);
|
||||
|
||||
expect(ticketBefore).toMatchObject({
|
||||
labelSingular: 'Ticket',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
const updatedTicketObject = {
|
||||
...ticketObject,
|
||||
labelSingular: 'Support Ticket',
|
||||
labelPlural: 'Support Tickets',
|
||||
description: 'A customer support ticket',
|
||||
icon: 'IconHeadset',
|
||||
};
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ objects: [updatedTicketObject] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterSecondSync = await findCustomObjects();
|
||||
const ticketAfter = objectsAfterSecondSync.find(
|
||||
(obj) => obj.nameSingular === 'ticket',
|
||||
);
|
||||
|
||||
expect(ticketAfter).toBeDefined();
|
||||
expect(ticketAfter).toMatchObject({
|
||||
nameSingular: 'ticket',
|
||||
labelSingular: 'Support Ticket',
|
||||
labelPlural: 'Support Tickets',
|
||||
description: 'A customer support ticket',
|
||||
icon: 'IconHeadset',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should delete an object when removed from manifest on second sync', async () => {
|
||||
const ticketObject = buildDefaultObjectManifest({
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
const invoiceObject = buildDefaultObjectManifest({
|
||||
nameSingular: 'invoice',
|
||||
namePlural: 'invoices',
|
||||
labelSingular: 'Invoice',
|
||||
labelPlural: 'Invoices',
|
||||
description: 'A billing invoice',
|
||||
icon: 'IconFileInvoice',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
objects: [ticketObject, invoiceObject],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterFirstSync = await findCustomObjects();
|
||||
|
||||
expect(
|
||||
objectsAfterFirstSync.find((obj) => obj.nameSingular === 'ticket'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
objectsAfterFirstSync.find((obj) => obj.nameSingular === 'invoice'),
|
||||
).toBeDefined();
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ objects: [ticketObject] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const objectsAfterSecondSync = await findCustomObjects();
|
||||
|
||||
expect(
|
||||
objectsAfterSecondSync.find((obj) => obj.nameSingular === 'ticket'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
objectsAfterSecondSync.find((obj) => obj.nameSingular === 'invoice'),
|
||||
).toBeUndefined();
|
||||
}, 60000);
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { findRoles } from 'test/integration/metadata/suites/role/utils/find-roles.util';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
const TEST_SECOND_ROLE_ID = uuidv4();
|
||||
|
||||
const buildManifest = (overrides?: Partial<Pick<Manifest, 'roles'>>) =>
|
||||
buildBaseManifest({
|
||||
appId: TEST_APP_ID,
|
||||
roleId: TEST_ROLE_ID,
|
||||
overrides: {
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
},
|
||||
],
|
||||
...overrides,
|
||||
},
|
||||
});
|
||||
|
||||
const findAppRoles = async () => {
|
||||
const { data } = await findRoles({
|
||||
gqlFields: 'id label description universalIdentifier',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
return data.getRoles.filter(
|
||||
(role) =>
|
||||
role.universalIdentifier === TEST_ROLE_ID ||
|
||||
role.universalIdentifier === TEST_SECOND_ROLE_ID,
|
||||
);
|
||||
};
|
||||
|
||||
describe('Manifest update - roles', () => {
|
||||
beforeEach(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Application',
|
||||
description: 'App for testing role manifest updates',
|
||||
sourcePath: 'test-manifest-update-role',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterEach(async () => {
|
||||
await uninstallApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new role when added to manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterFirstSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterFirstSync).toHaveLength(1);
|
||||
expect(rolesAfterFirstSync[0]).toMatchObject({
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_ROLE_ID,
|
||||
label: 'Viewer',
|
||||
description: 'Read-only access',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterSecondSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterSecondSync).toHaveLength(2);
|
||||
expect(
|
||||
rolesAfterSecondSync.find((r) => r.label === 'Editor'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
rolesAfterSecondSync.find((r) => r.label === 'Viewer'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
rolesAfterSecondSync.find((r) => r.label === 'Viewer'),
|
||||
).toMatchObject({
|
||||
description: 'Read-only access',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should update a role when properties change in manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterFirstSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterFirstSync).toHaveLength(1);
|
||||
expect(rolesAfterFirstSync[0]).toMatchObject({
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Senior Editor',
|
||||
description: 'Can edit everything',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterSecondSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterSecondSync).toHaveLength(1);
|
||||
expect(rolesAfterSecondSync[0]).toMatchObject({
|
||||
label: 'Senior Editor',
|
||||
description: 'Can edit everything',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should delete a role when removed from manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_ROLE_ID,
|
||||
label: 'Viewer',
|
||||
description: 'Read-only access',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterFirstSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterFirstSync).toHaveLength(2);
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const rolesAfterSecondSync = await findAppRoles();
|
||||
|
||||
expect(rolesAfterSecondSync).toHaveLength(1);
|
||||
expect(rolesAfterSecondSync[0]).toMatchObject({
|
||||
label: 'Editor',
|
||||
description: 'Can edit',
|
||||
});
|
||||
}, 60000);
|
||||
});
|
||||
@@ -0,0 +1,202 @@
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { findSkills } from 'test/integration/metadata/suites/skill/utils/find-skills.util';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
const TEST_SKILL_ID = uuidv4();
|
||||
const TEST_SECOND_SKILL_ID = uuidv4();
|
||||
|
||||
const SKILL_GQL_FIELDS = 'id name label description content icon applicationId';
|
||||
|
||||
const buildManifest = (overrides?: Partial<Pick<Manifest, 'skills'>>) =>
|
||||
buildBaseManifest({ appId: TEST_APP_ID, roleId: TEST_ROLE_ID, overrides });
|
||||
|
||||
const findAppSkills = async () => {
|
||||
const { data } = await findSkills({
|
||||
gqlFields: SKILL_GQL_FIELDS,
|
||||
expectToFail: false,
|
||||
input: undefined,
|
||||
});
|
||||
|
||||
return data.skills.filter(
|
||||
(skill) => skill.name === 'enrichment' || skill.name === 'scoring',
|
||||
);
|
||||
};
|
||||
|
||||
describe('Manifest update - skills', () => {
|
||||
beforeEach(async () => {
|
||||
await setupApplicationForSync({
|
||||
applicationUniversalIdentifier: TEST_APP_ID,
|
||||
name: 'Test Application',
|
||||
description: 'App for testing skill manifest updates',
|
||||
sourcePath: 'test-manifest-update-skill',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
afterEach(async () => {
|
||||
await uninstallApplication({
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
expectToFail: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new skill when added to manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({ skills: [] }),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterFirstSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterFirstSync).toHaveLength(0);
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
icon: 'IconSparkles',
|
||||
content: '# Enrichment\n\nEnrich your data.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterSecondSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterSecondSync).toHaveLength(1);
|
||||
expect(skillsAfterSecondSync[0]).toMatchObject({
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
icon: 'IconSparkles',
|
||||
content: '# Enrichment\n\nEnrich your data.',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should update a skill when properties change in manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
icon: 'IconSparkles',
|
||||
content: '# Enrichment\n\nEnrich your data.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterFirstSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterFirstSync).toHaveLength(1);
|
||||
expect(skillsAfterFirstSync[0]).toMatchObject({
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
});
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'enrichment',
|
||||
label: 'Data Enrichment',
|
||||
description: 'Enrich contact and company data',
|
||||
icon: 'IconSparkles',
|
||||
content:
|
||||
'# Data Enrichment\n\nEnrich your contact and company data from multiple sources.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterSecondSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterSecondSync).toHaveLength(1);
|
||||
expect(skillsAfterSecondSync[0]).toMatchObject({
|
||||
name: 'enrichment',
|
||||
label: 'Data Enrichment',
|
||||
description: 'Enrich contact and company data',
|
||||
content:
|
||||
'# Data Enrichment\n\nEnrich your contact and company data from multiple sources.',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should delete a skill when removed from manifest on second sync', async () => {
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
icon: 'IconSparkles',
|
||||
content: '# Enrichment\n\nEnrich your data.',
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_SKILL_ID,
|
||||
name: 'scoring',
|
||||
label: 'Lead Scoring',
|
||||
description: 'Score your leads',
|
||||
icon: 'IconChartBar',
|
||||
content: '# Lead Scoring\n\nScore leads automatically.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterFirstSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterFirstSync).toHaveLength(2);
|
||||
expect(
|
||||
skillsAfterFirstSync.find((s) => s.name === 'enrichment'),
|
||||
).toBeDefined();
|
||||
expect(
|
||||
skillsAfterFirstSync.find((s) => s.name === 'scoring'),
|
||||
).toBeDefined();
|
||||
|
||||
await syncApplication({
|
||||
manifest: buildManifest({
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
description: 'Enrich contact data',
|
||||
icon: 'IconSparkles',
|
||||
content: '# Enrichment\n\nEnrich your data.',
|
||||
},
|
||||
],
|
||||
}),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const skillsAfterSecondSync = await findAppSkills();
|
||||
|
||||
expect(skillsAfterSecondSync).toHaveLength(1);
|
||||
expect(skillsAfterSecondSync[0]).toMatchObject({
|
||||
name: 'enrichment',
|
||||
label: 'Enrichment',
|
||||
});
|
||||
expect(
|
||||
skillsAfterSecondSync.find((s) => s.name === 'scoring'),
|
||||
).toBeUndefined();
|
||||
}, 60000);
|
||||
});
|
||||
@@ -2,15 +2,19 @@ import { buildDefaultObjectManifest } from 'test/integration/metadata/suites/app
|
||||
import { setupApplicationForSync } from 'test/integration/metadata/suites/application/utils/setup-application-for-sync.util';
|
||||
import { syncApplication } from 'test/integration/metadata/suites/application/utils/sync-application.util';
|
||||
import { uninstallApplication } from 'test/integration/metadata/suites/application/utils/uninstall-application.util';
|
||||
import { findManyObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata.util';
|
||||
import { findRoles } from 'test/integration/metadata/suites/role/utils/find-roles.util';
|
||||
import { findSkills } from 'test/integration/metadata/suites/skill/utils/find-skills.util';
|
||||
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { type FieldManifest, type Manifest } from 'twenty-shared/application';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { buildBaseManifest } from 'test/integration/metadata/suites/application/utils/build-base-manifest.util';
|
||||
import { findManyObjectMetadataWithIndexes } from 'test/integration/metadata/suites/object-metadata/utils/find-many-object-metadata-with-indexes.util';
|
||||
|
||||
const TEST_APP_ID = uuidv4();
|
||||
const TEST_ROLE_ID = uuidv4();
|
||||
const TEST_SECOND_ROLE_ID = uuidv4();
|
||||
const TEST_FIELD_ID = uuidv4();
|
||||
const TEST_SKILL_ID = uuidv4();
|
||||
|
||||
@@ -23,6 +27,39 @@ const TEST_OBJECT = buildDefaultObjectManifest({
|
||||
icon: 'IconTicket',
|
||||
});
|
||||
|
||||
const TEST_SKILL = {
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'test-skill',
|
||||
label: 'Test Skill',
|
||||
description: 'A skill for testing',
|
||||
icon: 'IconBrain',
|
||||
content: '# Test Skill\n\nThis is a test skill.',
|
||||
};
|
||||
|
||||
const TEST_FIELD: FieldManifest = {
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
};
|
||||
|
||||
const buildManifest = (
|
||||
overrides?: Partial<Pick<Manifest, 'fields' | 'skills' | 'objects'>>,
|
||||
) =>
|
||||
buildBaseManifest({
|
||||
appId: TEST_APP_ID,
|
||||
roleId: TEST_ROLE_ID,
|
||||
overrides: {
|
||||
objects: [TEST_OBJECT],
|
||||
skills: [TEST_SKILL],
|
||||
fields: [TEST_FIELD],
|
||||
...overrides,
|
||||
},
|
||||
});
|
||||
|
||||
describe('syncApplication', () => {
|
||||
let appCreated = false;
|
||||
|
||||
@@ -49,48 +86,8 @@ describe('syncApplication', () => {
|
||||
});
|
||||
|
||||
it('should return workspace migration actions on initial sync then on second sync with field rename and new role', async () => {
|
||||
const initialManifest: Manifest = {
|
||||
application: {
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
defaultRoleUniversalIdentifier: TEST_ROLE_ID,
|
||||
displayName: 'Test Application',
|
||||
description: 'A test application for workspace migration',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
apiClientChecksum: null,
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
skills: [],
|
||||
objects: [TEST_OBJECT],
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
views: [],
|
||||
navigationMenuItems: [],
|
||||
pageLayouts: [],
|
||||
};
|
||||
|
||||
const { data: firstSyncData } = await syncApplication({
|
||||
manifest: initialManifest,
|
||||
manifest: buildManifest(),
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
@@ -98,139 +95,92 @@ describe('syncApplication', () => {
|
||||
extractRecordIdsAndDatesAsExpectAny(firstSyncData),
|
||||
);
|
||||
|
||||
const updatedManifest: Manifest = {
|
||||
...initialManifest,
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
{
|
||||
universalIdentifier: TEST_SECOND_ROLE_ID,
|
||||
label: 'Viewer Role',
|
||||
description: 'A read-only role',
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
universalIdentifier: TEST_FIELD_ID,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
objectUniversalIdentifier: TEST_OBJECT.universalIdentifier,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data: secondSyncData } = await syncApplication({
|
||||
manifest: updatedManifest,
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(secondSyncData).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(secondSyncData),
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
it('should sync a skill then update it on second sync', async () => {
|
||||
const initialManifest: Manifest = {
|
||||
application: {
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
defaultRoleUniversalIdentifier: TEST_ROLE_ID,
|
||||
displayName: 'Test Application',
|
||||
description: 'A test application for workspace migration',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
apiClientChecksum: null,
|
||||
// Verify database state after first sync
|
||||
const { objects: objectsAfterSync } = await findManyObjectMetadata({
|
||||
input: {
|
||||
filter: { isCustom: { is: true } },
|
||||
paging: { first: 100 },
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'test-skill',
|
||||
label: 'Test Skill',
|
||||
description: 'A skill for testing',
|
||||
icon: 'IconBrain',
|
||||
content: '# Test Skill\n\nThis is a test skill.',
|
||||
},
|
||||
],
|
||||
objects: [],
|
||||
fields: [],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
views: [],
|
||||
navigationMenuItems: [],
|
||||
pageLayouts: [],
|
||||
};
|
||||
|
||||
const { data: firstSyncData } = await syncApplication({
|
||||
manifest: initialManifest,
|
||||
gqlFields:
|
||||
'id nameSingular namePlural labelSingular labelPlural description icon isCustom',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(firstSyncData).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(firstSyncData),
|
||||
const ticketObject = objectsAfterSync.find(
|
||||
(obj) => obj.nameSingular === 'ticket',
|
||||
);
|
||||
|
||||
const updatedManifest: Manifest = {
|
||||
...initialManifest,
|
||||
skills: [
|
||||
{
|
||||
universalIdentifier: TEST_SKILL_ID,
|
||||
name: 'test-skill',
|
||||
label: 'Test Skill Updated',
|
||||
description: 'An updated skill for testing',
|
||||
icon: 'IconBrain',
|
||||
content:
|
||||
'# Test Skill\n\nThis is an updated test skill with more content.',
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(ticketObject).toBeDefined();
|
||||
expect(ticketObject).toMatchObject({
|
||||
nameSingular: 'ticket',
|
||||
namePlural: 'tickets',
|
||||
labelSingular: 'Ticket',
|
||||
labelPlural: 'Tickets',
|
||||
description: 'A support ticket',
|
||||
icon: 'IconTicket',
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
const { data: secondSyncData } = await syncApplication({
|
||||
manifest: updatedManifest,
|
||||
const objects = await findManyObjectMetadataWithIndexes({
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
expect(secondSyncData).toMatchSnapshot(
|
||||
extractRecordIdsAndDatesAsExpectAny(secondSyncData),
|
||||
const fieldsAfterSync = objects.find(
|
||||
(o) => o.universalIdentifier === TEST_OBJECT.universalIdentifier,
|
||||
)?.fieldsList;
|
||||
|
||||
const descriptionField = fieldsAfterSync?.find(
|
||||
(f) => f.universalIdentifier === TEST_FIELD_ID,
|
||||
);
|
||||
|
||||
expect(descriptionField).toBeDefined();
|
||||
expect(descriptionField).toMatchObject({
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
type: FieldMetadataType.TEXT,
|
||||
description: 'Ticket description',
|
||||
icon: 'IconFileDescription',
|
||||
});
|
||||
|
||||
const { data: rolesAfterSync } = await findRoles({
|
||||
gqlFields: 'id label description universalIdentifier',
|
||||
expectToFail: false,
|
||||
});
|
||||
|
||||
const testRole = rolesAfterSync.getRoles.find(
|
||||
(role) => role.universalIdentifier === TEST_ROLE_ID,
|
||||
);
|
||||
|
||||
const { data: skillsAfterSync } = await findSkills({
|
||||
gqlFields: 'id name label description content icon',
|
||||
expectToFail: false,
|
||||
input: undefined,
|
||||
});
|
||||
|
||||
const testSkill = skillsAfterSync.skills.find(
|
||||
(skill) => skill.name === 'test-skill',
|
||||
);
|
||||
|
||||
expect(testRole).toBeDefined();
|
||||
expect(testRole).toMatchObject({
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
});
|
||||
|
||||
expect(testSkill).toBeDefined();
|
||||
expect(testSkill).toMatchObject({
|
||||
name: 'test-skill',
|
||||
label: 'Test Skill',
|
||||
description: 'A skill for testing',
|
||||
icon: 'IconBrain',
|
||||
content: '# Test Skill\n\nThis is a test skill.',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
it('should create a TEXT field on the standard Company object', async () => {
|
||||
const companyFieldId = uuidv4();
|
||||
|
||||
const manifest: Manifest = {
|
||||
application: {
|
||||
universalIdentifier: TEST_APP_ID,
|
||||
defaultRoleUniversalIdentifier: TEST_ROLE_ID,
|
||||
displayName: 'Test Application',
|
||||
description: 'A test application for workspace migration',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
apiClientChecksum: null,
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: TEST_ROLE_ID,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
const manifest = buildManifest({
|
||||
skills: [],
|
||||
objects: [],
|
||||
fields: [
|
||||
@@ -245,13 +195,7 @@ describe('syncApplication', () => {
|
||||
STANDARD_OBJECTS.company.universalIdentifier,
|
||||
},
|
||||
],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
views: [],
|
||||
navigationMenuItems: [],
|
||||
pageLayouts: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { data: syncData } = await syncApplication({
|
||||
manifest,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
|
||||
export const buildBaseManifest = ({
|
||||
appId,
|
||||
roleId,
|
||||
overrides,
|
||||
}: {
|
||||
appId: string;
|
||||
roleId: string;
|
||||
overrides?: Partial<Manifest>;
|
||||
}): Manifest => ({
|
||||
application: {
|
||||
universalIdentifier: appId,
|
||||
defaultRoleUniversalIdentifier: roleId,
|
||||
displayName: 'Test Application',
|
||||
description: 'Test application',
|
||||
icon: 'IconTestPipe',
|
||||
applicationVariables: {},
|
||||
packageJsonChecksum: null,
|
||||
yarnLockChecksum: null,
|
||||
apiClientChecksum: null,
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
universalIdentifier: roleId,
|
||||
label: 'Test Role',
|
||||
description: 'A test role',
|
||||
},
|
||||
],
|
||||
skills: [],
|
||||
objects: [],
|
||||
fields: [],
|
||||
logicFunctions: [],
|
||||
frontComponents: [],
|
||||
publicAssets: [],
|
||||
views: [],
|
||||
navigationMenuItems: [],
|
||||
pageLayouts: [],
|
||||
...overrides,
|
||||
});
|
||||
@@ -22,10 +22,15 @@ export const findManyObjectMetadataWithIndexes = async ({
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
universalIdentifier
|
||||
fieldsList {
|
||||
id
|
||||
type
|
||||
name
|
||||
description
|
||||
icon
|
||||
label
|
||||
universalIdentifier
|
||||
relation {
|
||||
type
|
||||
sourceObjectMetadata {
|
||||
|
||||
@@ -5,6 +5,7 @@ const DEFAULT_ROLE_GQL_FIELDS = `
|
||||
label
|
||||
description
|
||||
icon
|
||||
universalIdentifier
|
||||
isEditable
|
||||
canUpdateAllSettings
|
||||
canAccessAllTools
|
||||
|
||||
@@ -7,6 +7,12 @@ describe('computeMetadataNameFromLabel', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should convert a label to camelCase', () => {
|
||||
expect(computeMetadataNameFromLabel({ label: 'My CreatedAt Field' })).toBe(
|
||||
'myCreatedatField',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return empty string for empty label', () => {
|
||||
expect(computeMetadataNameFromLabel({ label: '' })).toBe('');
|
||||
});
|
||||
|
||||
@@ -68,22 +68,22 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: 'b8d4f9a3-0c25-4e7b-9f6a-2d3e4c5b6f70',
|
||||
},
|
||||
noteIdIndex: {
|
||||
universalIdentifier: 'c9e5a0b4-1d36-4f8c-0a7b-3e4f5d6c7a81',
|
||||
universalIdentifier: '9d31ea73-13b6-4e06-84ee-c66c72bf7787',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: 'd0f6b1c5-2e47-4a9d-1b8c-4f5a6e7d8b92',
|
||||
universalIdentifier: '55637a5a-1edc-4351-8d76-d40020bf8944',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: 'e1a7c2d6-3f58-4b0e-2c9d-5a6b7f8e9c03',
|
||||
universalIdentifier: '4137ba06-184d-438f-b484-080f02a97659',
|
||||
},
|
||||
opportunityIdIndex: {
|
||||
universalIdentifier: 'f2b8d3e7-4a69-4c1f-3d0e-6b7c8a9f0d14',
|
||||
universalIdentifier: '8cc162d1-c127-4981-878d-f78622f8f12d',
|
||||
},
|
||||
dashboardIdIndex: {
|
||||
universalIdentifier: '03c9e4f8-5b70-4d2a-4e1f-7c8d9b0a1e25',
|
||||
universalIdentifier: 'c10eba2d-ff1a-4eab-9285-50481c12a003',
|
||||
},
|
||||
workflowIdIndex: {
|
||||
universalIdentifier: '14d0f5a9-6c81-4e3b-5f2a-8d9e0c1b2f36',
|
||||
universalIdentifier: 'fadeab4b-79ee-4173-af79-72c51fbad888',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -159,7 +159,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
workspaceMemberIdIndex: {
|
||||
universalIdentifier: '25e1a6b0-7d92-4f4c-6a3b-9e0f1d2c3a47',
|
||||
universalIdentifier: '4daf320e-74d0-4f24-a45a-af3a09d741cb',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -205,7 +205,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
calendarChannelIdIndex: {
|
||||
universalIdentifier: '36f2b7c1-8e03-4a5d-7b4c-0f1a2e3d4b58',
|
||||
universalIdentifier: 'ff6b86c1-3112-4dfa-b734-c4789111a716',
|
||||
},
|
||||
calendarEventIdIndex: {
|
||||
universalIdentifier: '47a3c8d2-9f14-4b6e-8c5d-1a2b3f4e5c69',
|
||||
@@ -334,13 +334,13 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
calendarEventIdIndex: {
|
||||
universalIdentifier: '69c5e0f4-1b36-4d8a-0e7f-3c4d5b6a7e81',
|
||||
universalIdentifier: 'c458ad97-8b95-43de-9003-88eb68576049',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: '70d6f1a5-2c47-4e9b-1f8a-4d5e6c7b8f92',
|
||||
universalIdentifier: '30e9b75a-881f-4a85-aaf1-f2d2464be1cf',
|
||||
},
|
||||
workspaceMemberIdIndex: {
|
||||
universalIdentifier: '81e7a2b6-3d58-4f0c-2a9b-5e6f7d8c9003',
|
||||
universalIdentifier: '898aa202-428f-4a7a-a3b3-8f0a17a6658e',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -507,13 +507,13 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
accountOwnerIdIndex: {
|
||||
universalIdentifier: '92f8b3c7-4e69-4a1d-3b0c-6f7a8e9d0114',
|
||||
universalIdentifier: 'ec2ebfc9-0c9b-4597-a87d-aa295e2d8bfe',
|
||||
},
|
||||
domainNameUniqueIndex: {
|
||||
universalIdentifier: 'a3a9c4d8-5f70-4b2e-4c1d-7a8b9f0e1225',
|
||||
universalIdentifier: 'dd300c61-f422-467a-91f4-de4f83c4175b',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: 'b4b0d5e9-6a81-4c3f-5d2e-8b9c0a1f2336',
|
||||
universalIdentifier: 'c3eb62df-2cc1-4cc3-b7aa-e96a4d65c633',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -661,7 +661,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
accountOwnerIdIndex: {
|
||||
universalIdentifier: 'c5c1e6f0-7b92-4d4a-6e3f-9c0d1b2a3447',
|
||||
universalIdentifier: '8e7ca28e-6002-4304-9dcc-0a8da93ca198',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -690,7 +690,7 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '53ee42e7-f157-42b5-b278-a5fa9b378307',
|
||||
},
|
||||
timelineActivities: {
|
||||
universalIdentifier: '20202020-9b0c-5d6e-7f8a-9b0c1d2e3f4a',
|
||||
universalIdentifier: '99c330c0-5b7d-4276-a764-aed84499dfb5',
|
||||
},
|
||||
favorites: {
|
||||
universalIdentifier: '20202020-f032-478f-88fa-6426ff6f1e4c',
|
||||
@@ -704,7 +704,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: 'd6d2f7a1-8c03-4e5b-7f4a-0d1e2c3b4558',
|
||||
universalIdentifier: 'e69f71aa-de0f-4b70-845f-7a8369c47928',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -783,28 +783,28 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: 'f8f4b9c3-0e25-4a7d-9b6c-2f3a4e5d677a',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '0905c0d4-1f36-4b8e-0c7d-3a4b5f6e788b',
|
||||
universalIdentifier: '1f360393-a336-435d-966d-8ec2645f875c',
|
||||
},
|
||||
favoriteFolderIdIndex: {
|
||||
universalIdentifier: '1016d1e5-2a47-4c9f-1d8e-4b5c6a7f899c',
|
||||
universalIdentifier: 'c0369a13-49bd-48b0-a9f0-6ed0ee8e1b09',
|
||||
},
|
||||
opportunityIdIndex: {
|
||||
universalIdentifier: '2127e2f6-3b58-4d0a-2e9f-5c6d7b80900d',
|
||||
universalIdentifier: 'b8e9f696-5be4-48cb-815c-7c0bb8b69d38',
|
||||
},
|
||||
workflowIdIndex: {
|
||||
universalIdentifier: '3238f3a7-4c69-4e1b-3f0a-6d7e8c91011e',
|
||||
universalIdentifier: 'd4f3ef9f-ae24-4cef-9d7a-684a24d4968b',
|
||||
},
|
||||
workflowVersionIdIndex: {
|
||||
universalIdentifier: '4349a4b8-5d70-4f2c-4a1b-7e8f9d02122f',
|
||||
universalIdentifier: 'a257158e-3a89-4715-970c-7fc38ac22370',
|
||||
},
|
||||
workflowRunIdIndex: {
|
||||
universalIdentifier: '5450b5c9-6e81-4a3d-5b2c-8f90ae132340',
|
||||
universalIdentifier: 'd55711a3-3297-4fef-beab-48d733712a33',
|
||||
},
|
||||
taskIdIndex: {
|
||||
universalIdentifier: '6561c6d0-7f92-4b4e-6c3d-90a1bf243451',
|
||||
universalIdentifier: 'f30c9a75-a563-45a2-93b8-84f59004de8f',
|
||||
},
|
||||
noteIdIndex: {
|
||||
universalIdentifier: '7672d7e1-8003-4c5f-7d4e-01b2c0354562',
|
||||
universalIdentifier: 'dca73027-adbe-4078-ae47-8d17850b9f2b',
|
||||
},
|
||||
dashboardIdIndex: {
|
||||
universalIdentifier: '8783e8f2-9114-4d6a-8e5f-12c3d1465673',
|
||||
@@ -889,7 +889,7 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: 'edddd409-d9f0-4b93-8e3f-37faef6a3387',
|
||||
},
|
||||
messageFolders: {
|
||||
universalIdentifier: '20202020-c3d4-e5f6-a7b8-901234567890',
|
||||
universalIdentifier: '9bfc9da7-ae2d-44fd-9563-ede90c5d6222',
|
||||
},
|
||||
},
|
||||
indexes: {
|
||||
@@ -897,10 +897,10 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '9894f9a3-0225-4e7b-9f6a-23d4e2576784',
|
||||
},
|
||||
messageIdIndex: {
|
||||
universalIdentifier: '0905a0b4-1336-4f8c-0a7b-34e5f3687895',
|
||||
universalIdentifier: '9bb24d40-60dd-4beb-8c64-a74e8c67f9ee',
|
||||
},
|
||||
messageChannelIdMessageIdUniqueIndex: {
|
||||
universalIdentifier: '1a16b1c5-2447-4a9d-1b8c-45f6a47989a6',
|
||||
universalIdentifier: '1b86ece8-7ce3-4df3-8771-fd4b5d45b2f2',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -932,21 +932,21 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '38633a97-0e88-44de-9903-b8c9e0f59a36',
|
||||
},
|
||||
messageChannelMessageAssociation: {
|
||||
universalIdentifier: '20202020-d4e5-f6a7-b8c9-012345678901',
|
||||
universalIdentifier: '7411cfa3-4fd9-4b90-a636-940015fd7243',
|
||||
},
|
||||
messageFolder: {
|
||||
universalIdentifier: '20202020-e5f6-a7b8-c9d0-123456789012',
|
||||
universalIdentifier: 'b3369d31-3856-4a7a-b007-ee353918127c',
|
||||
},
|
||||
},
|
||||
indexes: {
|
||||
messageChannelMessageAssociationIdIndex: {
|
||||
universalIdentifier: '2b38d3e7-5779-4c1f-4e0f-78c9d70d1de9',
|
||||
universalIdentifier: '8e6038aa-1f79-4a84-87b5-f33caa172e98',
|
||||
},
|
||||
messageFolderIdIndex: {
|
||||
universalIdentifier: '3c49e4f8-6880-4d2a-5f1a-89d0e81e2ef0',
|
||||
universalIdentifier: '905299c3-ca81-435d-901c-f68b87562516',
|
||||
},
|
||||
messageChannelMessageAssociationIdMessageFolderIdUniqueIndex: {
|
||||
universalIdentifier: '4d50f5a9-7991-4e3b-6a2b-90e1f92f3f01',
|
||||
universalIdentifier: 'a3de1788-5dff-4849-ac5a-0dabe5fab216',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1036,7 +1036,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
connectedAccountIdIndex: {
|
||||
universalIdentifier: '2b27c2d6-3558-4b0e-2c9d-56a7b58a9ab7',
|
||||
universalIdentifier: 'ab09a386-4dcc-41f7-8dc6-a6071e9c64b7',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1088,12 +1088,12 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '5f2d3937-bafd-4d71-b4cb-b34037efd2e1',
|
||||
},
|
||||
messageChannelMessageAssociationMessageFolders: {
|
||||
universalIdentifier: '20202020-f6a7-b8c9-d0e1-234567890123',
|
||||
universalIdentifier: 'cce5ce1e-31d0-42e6-83cd-90059244a484',
|
||||
},
|
||||
},
|
||||
indexes: {
|
||||
messageChannelIdIndex: {
|
||||
universalIdentifier: '3c38d3e7-4669-4c1f-3d0e-67b8c69b0bc8',
|
||||
universalIdentifier: '6217f2a5-28ac-4b88-8a2a-45eee4580e57',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1143,13 +1143,13 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
messageIdIndex: {
|
||||
universalIdentifier: '4d49e4f8-5770-4d2a-4e1f-78c9d70c1cd9',
|
||||
universalIdentifier: 'ab0863ba-f95e-493c-b86c-56e1bc7e5bc2',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: '5e50f5a9-6881-4e3b-5f2a-89d0e81d2de0',
|
||||
universalIdentifier: 'df805c2e-3bfe-4d51-8309-75e5eb4052fe',
|
||||
},
|
||||
workspaceMemberIdIndex: {
|
||||
universalIdentifier: '6f61a6b0-7992-4f4c-6a3b-90e1f92e3ef1',
|
||||
universalIdentifier: 'ce1e3a9e-afe9-439d-abb7-6cc98a6fa405',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1248,7 +1248,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
messageThreadIdIndex: {
|
||||
universalIdentifier: '7072b7c1-8003-4a5d-7b4c-01f2a03f4f02',
|
||||
universalIdentifier: '7a05b45e-7aa6-4a7e-9bbc-299cbed53c96',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -1415,13 +1415,13 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '9294d9e3-0225-4c7f-9d6e-23b4c25b6b24',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: '0305e0f4-1336-4d8a-0e7f-34c5d36c7c35',
|
||||
universalIdentifier: '7c069dc0-e83b-4cd5-aaa2-cac7f3e00d80',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '1416f1a5-2447-4e9b-1f8a-45d6e47d8d46',
|
||||
universalIdentifier: '2d83909a-a383-4e82-b00a-8b7739f3f906',
|
||||
},
|
||||
opportunityIdIndex: {
|
||||
universalIdentifier: '2527a2b6-3558-4f0c-2a9b-56e7f58e9e57',
|
||||
universalIdentifier: '0d1a59b4-cc87-4b7d-804a-656e8504f371',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -1501,16 +1501,16 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
pointOfContactIdIndex: {
|
||||
universalIdentifier: '3638b3c7-4669-4a1d-3b0c-67f8a69f0f68',
|
||||
universalIdentifier: 'b8c2a673-a981-4357-a43d-313a358e4daa',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '4749c4d8-5770-4b2e-4c1d-78a9b70a1a79',
|
||||
universalIdentifier: 'e161072d-37b1-477a-b944-ef0d65289574',
|
||||
},
|
||||
stageIndex: {
|
||||
universalIdentifier: '5850d5e9-6881-4c3f-5d2e-89b0c81b2b80',
|
||||
universalIdentifier: 'ae60d580-b562-44f2-a24d-7b8040063f83',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '6961e6f0-7992-4d4a-6e3f-90c1d92c3c91',
|
||||
universalIdentifier: 'f53fdd28-a26b-47ba-81b5-6813ad622720',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -1685,7 +1685,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '7072f7a1-8003-4e5b-7f4a-01d2e03d4d02',
|
||||
universalIdentifier: '8a265a5c-d3ae-47dc-bdf9-b42cfa2ba639',
|
||||
},
|
||||
emailsUniqueIndex: {
|
||||
universalIdentifier: '8183a8b2-9114-4f6c-8a5b-12e3f14e5e13',
|
||||
@@ -1821,10 +1821,10 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
assigneeIdIndex: {
|
||||
universalIdentifier: '0305c0d4-1336-4b8e-0c7d-34a5b36a7a35',
|
||||
universalIdentifier: 'f48fa3b1-0cec-44da-a9e5-f8a5e766637e',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '1416d1e5-2447-4c9f-1d8e-45b6c47b8b46',
|
||||
universalIdentifier: 'a86b32b3-01d3-4302-a152-8b7f247db7b4',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2010,16 +2010,16 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
taskIdIndex: {
|
||||
universalIdentifier: '2527e2f6-3558-4d0a-2e9f-56c7d58c9c57',
|
||||
universalIdentifier: 'c882f7a4-b025-4d32-aa26-5ef2595bdbf9',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: '3638f3a7-4669-4e1b-3f0a-67d8e69d0d68',
|
||||
universalIdentifier: 'b7d305d1-6fae-4ed6-9bdc-354fe9032c0e',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '4749a4b8-5770-4f2c-4a1b-78e9f70e1e79',
|
||||
universalIdentifier: 'c0af54c7-751b-4bb2-b102-677cc4e47402',
|
||||
},
|
||||
opportunityIdIndex: {
|
||||
universalIdentifier: '5850b5c9-6881-4a3d-5b2c-89f0a81f2f80',
|
||||
universalIdentifier: '6942e0ba-90f6-4c33-bf40-7f00b1ec35ab',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2122,10 +2122,10 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
workspaceMemberIdIndex: {
|
||||
universalIdentifier: '6961c6d0-7992-4b4e-6c3d-90a1b92a3a91',
|
||||
universalIdentifier: '5e0b2391-85ca-4a66-aef4-52d74245bec2',
|
||||
},
|
||||
personIdIndex: {
|
||||
universalIdentifier: '7072d7e1-8003-4c5f-7d4e-01b2c03b4b02',
|
||||
universalIdentifier: '3e89a914-7bec-47bd-9cf9-743c6b83d001',
|
||||
},
|
||||
companyIdIndex: {
|
||||
universalIdentifier: '8183e8f2-9114-4d6a-8e5f-12c3d14c5c13',
|
||||
@@ -2134,22 +2134,22 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '9294f9a3-0225-4e7b-9f6a-23d4e25d6d24',
|
||||
},
|
||||
noteIdIndex: {
|
||||
universalIdentifier: '0305a0b4-1336-4f8c-0a7b-34e5f36e7e35',
|
||||
universalIdentifier: '995db1d8-0d3e-40f7-b0eb-5e6897bc9966',
|
||||
},
|
||||
taskIdIndex: {
|
||||
universalIdentifier: '1416b1c5-2447-4a9d-1b8c-45f6a47f8f46',
|
||||
universalIdentifier: '609cf622-86ef-48d1-812b-e1cab610a46c',
|
||||
},
|
||||
workflowIdIndex: {
|
||||
universalIdentifier: '2527c2d6-3558-4b0e-2c9d-56a7b58a9a57',
|
||||
universalIdentifier: 'd6059ec2-92b0-4cfc-9fd8-78050f03108f',
|
||||
},
|
||||
workflowVersionIdIndex: {
|
||||
universalIdentifier: '3638d3e7-4669-4c1f-3d0e-67b8c69b0b68',
|
||||
universalIdentifier: 'd94329b3-5dc8-4141-ae28-31afe28f7135',
|
||||
},
|
||||
workflowRunIdIndex: {
|
||||
universalIdentifier: '4749e4f8-5770-4d2a-4e1f-78c9d70c1c79',
|
||||
universalIdentifier: '1a2bd046-7c23-4e0a-9f8a-c3ca3a16d3b9',
|
||||
},
|
||||
dashboardIdIndex: {
|
||||
universalIdentifier: '5850f5a9-6881-4e3b-5f2a-89d0e81d2d80',
|
||||
universalIdentifier: 'e8821da9-728d-470a-bf5b-5a981fff7880',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2247,7 +2247,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '6961a6b0-7992-4f4c-6a3b-90e1f92e3e91',
|
||||
universalIdentifier: 'c7e64c55-eb0c-4b93-b076-5cfcf2e2e042',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2315,7 +2315,7 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
workflowIdIndex: {
|
||||
universalIdentifier: '7072b7c1-8003-4a5d-7b4c-01f2a03f4f03',
|
||||
universalIdentifier: '7331ff89-a3f9-4ac0-9fa9-0de5663ae7b2',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -2377,7 +2377,7 @@ export const STANDARD_OBJECTS = {
|
||||
universalIdentifier: '9294d9e3-0225-4c7f-9d6e-23b4c25b6b25',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '0305e0f4-1336-4d8a-0e7f-34c5d36c7c36',
|
||||
universalIdentifier: 'e0ac5ad2-d0c8-4f72-b710-8e53b9dc18d9',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2492,10 +2492,10 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
workflowIdIndex: {
|
||||
universalIdentifier: '1416f1a5-2447-4e9b-1f8a-45d6e47d8d47',
|
||||
universalIdentifier: '8138c3b3-0b14-4ee1-be0e-debdde6b3219',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '2527a2b6-3558-4f0c-2a9b-56e7f58e9e58',
|
||||
universalIdentifier: '6f3a65eb-2aee-4108-b8a0-c62da419d1dc',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
@@ -2638,10 +2638,10 @@ export const STANDARD_OBJECTS = {
|
||||
},
|
||||
indexes: {
|
||||
userEmailUniqueIndex: {
|
||||
universalIdentifier: '3638b3c7-4669-4a1d-3b0c-67f8a69f0f69',
|
||||
universalIdentifier: '76da5f27-523c-44b6-ad06-12954f6b949f',
|
||||
},
|
||||
searchVectorGinIndex: {
|
||||
universalIdentifier: '4749c4d8-5770-4b2e-4c1d-78a9b70a1a7a',
|
||||
universalIdentifier: '8678dde9-a804-4a9e-80e3-9af35e471ec5',
|
||||
},
|
||||
},
|
||||
views: {
|
||||
|
||||
Reference in New Issue
Block a user