Add manifest integration tests (#18203)

- add integration test on sync manifest endpoint
- fix invalid standard uuid + migration command
This commit is contained in:
martmull
2026-02-25 10:37:50 +01:00
committed by GitHub
parent b84a8588be
commit f080b952eb
30 changed files with 1916 additions and 473 deletions

View File

@@ -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();
}
}
}

View File

@@ -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 {}

View File

@@ -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 = {

View File

@@ -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,
});
}

View File

@@ -31,7 +31,7 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({
isSearchable: false,
duplicateCriteria: null,
shortcut: null,
isLabelSyncedWithName: true,
isLabelSyncedWithName: false,
fieldUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
viewUniversalIdentifiers: [],

View File

@@ -18,6 +18,7 @@ export class CreateFieldInput extends OmitType(
'standardOverrides',
'applicationId',
'morphId',
'universalIdentifier',
] as const,
InputType,
) {

View File

@@ -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)

View File

@@ -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,

View File

@@ -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);
});
});

View File

@@ -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
);
};

View File

@@ -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,

View File

@@ -66,7 +66,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({
if (
universalFlatObjectMetadataToValidate.isLabelSyncedWithName &&
!areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectdMetadata: universalFlatObjectMetadataToValidate,
flatObjectMetadata: universalFlatObjectMetadataToValidate,
buildOptions,
})
) {

View File

@@ -32,6 +32,9 @@ export class ObjectMetadataDTO {
@IDField(() => UUIDScalarType)
id: string;
@IDField(() => UUIDScalarType)
universalIdentifier: string;
@Field()
nameSingular: string;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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 }>;

View File

@@ -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>>;

View File

@@ -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",
}
`;

View File

@@ -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>,
},
}
`;

View File

@@ -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,

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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,

View File

@@ -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,
});

View File

@@ -22,10 +22,15 @@ export const findManyObjectMetadataWithIndexes = async ({
gqlFields: `
id
nameSingular
universalIdentifier
fieldsList {
id
type
name
description
icon
label
universalIdentifier
relation {
type
sourceObjectMetadata {

View File

@@ -5,6 +5,7 @@ const DEFAULT_ROLE_GQL_FIELDS = `
label
description
icon
universalIdentifier
isEditable
canUpdateAllSettings
canAccessAllTools

View File

@@ -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('');
});

View File

@@ -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: {