Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -115,6 +117,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
this.addMissingSystemFieldsToStandardObjectsCommand,
this.backfillMessageChannelMessageAssociationMessageFolderCommand,
this.backfillPageLayoutsCommand,
this.fixRoleAndAgentUniversalIdentifiersCommand,
];

this.allCommands = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({
isSearchable: false,
duplicateCriteria: null,
shortcut: null,
isLabelSyncedWithName: true,
isLabelSyncedWithName: false,
fieldUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class CreateFieldInput extends OmitType(
'standardOverrides',
'applicationId',
'morphId',
'universalIdentifier',
] as const,
InputType,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (
isUnique,
settings,
id,
universalIdentifier,
label,
name,
objectMetadataId,
Expand All @@ -32,6 +33,7 @@ export const fromFlatFieldMetadataToFieldMetadataDto = (

return {
id,
universalIdentifier,
label,
name,
objectMetadataId,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -25,7 +25,7 @@ export const areFlatObjectMetadataNamesSyncedWithLabels = ({
);

return (
flatObjectdMetadata.nameSingular === computedSingularName &&
flatObjectdMetadata.namePlural === computedPluralName
flatObjectMetadata.nameSingular === computedSingularName &&
flatObjectMetadata.namePlural === computedPluralName
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
shortcut,
duplicateCriteria,
id,
universalIdentifier,
isActive,
isCustom,
isLabelSyncedWithName,
Expand All @@ -32,6 +33,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (

return {
id,
universalIdentifier,
isActive,
isCustom,
isLabelSyncedWithName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const validateFlatObjectMetadataNameAndLabels = ({
if (
universalFlatObjectMetadataToValidate.isLabelSyncedWithName &&
!areFlatObjectMetadataNamesSyncedWithLabels({
flatObjectdMetadata: universalFlatObjectMetadataToValidate,
flatObjectMetadata: universalFlatObjectMetadataToValidate,
buildOptions,
})
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class ObjectMetadataDTO {
@IDField(() => UUIDScalarType)
id: string;

@IDField(() => UUIDScalarType)
universalIdentifier: string;

@Field()
nameSingular: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }>;
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>>;
Expand Down
Loading
Loading