[REQUIRES_CACHE_FLUSH_FOR_FIELD_AND_OBJECT]FlatFieldMetadata and FlatObjectMetadata required universal (#17557)

# Introduction
In this PR we're migrating both the `field` and `object` metadata to be
using the new `FlatEntityFromV2` that requires all the
`UniversalFlatEntityExtraProperties` to be spread at the flat entity
root.

This means that we have to update all of their flat declaration

This type swap allows to isole a specific entity migration into his own
type scope and avoid to have everything handled at once

```ts
/**
 * Currently under migration but aims to replace FlatEntity afterwards
 */
export type FlatEntityFromV2<
  TEntity,
  TMetadataName extends AllMetadataName | undefined = undefined,
  TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom<
    TEntity,
    TMetadataName
  >,
> = Omit<TInnerFlatEntity, '__universal'> & TInnerFlatEntity['__universal'];

```

## Impact
Both object and field:
- Create input transpilation utils
- from entity to flat tools
- mocks

## Note
Removed from the universal extra properties the jsonb properties that do
not contain a serialized

## Next
Next step is to incrementally make the builder and runner expect
`UniversalFlatEntity` for both of these metadata
This way we will be able to fully migrate an entity e2e typesafely
This commit is contained in:
Paul Rastoin
2026-01-29 19:11:19 +01:00
committed by GitHub
parent e91457a47e
commit fbbd8fe967
69 changed files with 1244 additions and 498 deletions
@@ -14,6 +14,7 @@ export const FieldMetadataExceptionCode = appendCommonExceptionCode({
FIELD_MUTATION_NOT_ALLOWED: 'FIELD_MUTATION_NOT_ALLOWED',
FIELD_ALREADY_EXISTS: 'FIELD_ALREADY_EXISTS',
OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND',
APPLICATION_NOT_FOUND: 'APPLICATION_NOT_FOUND',
FIELD_METADATA_RELATION_NOT_ENABLED: 'FIELD_METADATA_RELATION_NOT_ENABLED',
FIELD_METADATA_RELATION_MALFORMED: 'FIELD_METADATA_RELATION_MALFORMED',
LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND:
@@ -43,6 +44,8 @@ const getFieldMetadataExceptionUserFriendlyMessage = (
return msg`A field with this name already exists.`;
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
return msg`Object not found.`;
case FieldMetadataExceptionCode.APPLICATION_NOT_FOUND:
return msg`Application not found.`;
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
return msg`Relation is not enabled for this field.`;
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
@@ -31,6 +31,7 @@ import { ViewModule } from 'src/engine/metadata-modules/view/view.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { FieldMetadataEntity } from './field-metadata.entity';
@@ -62,6 +63,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
FlatFieldMetadataModule,
IndexMetadataModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceCacheModule,
],
services: [FieldMetadataService],
resolvers: [
@@ -22,6 +22,7 @@ import { fromCreateFieldInputToFlatFieldMetadatasToCreate } from 'src/engine/met
import { fromDeleteFieldInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util';
import { fromUpdateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-update-field-input-to-flat-field-metadata.util';
import { throwOnFieldInputTranspilationsError } from 'src/engine/metadata-modules/flat-field-metadata/utils/throw-on-field-input-transpilations-error.util';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { EMPTY_ORCHESTRATOR_FAILURE_REPORT } from 'src/engine/workspace-manager/workspace-migration/constant/empty-orchestrator-failure-report.constant';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@@ -34,6 +35,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {
super(fieldMetadataRepository);
}
@@ -180,7 +182,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps: existingFlatViewMaps,
flatViewFieldMaps: existingFlatViewFieldMaps,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
flatApplication: workspaceCustomFlatApplication,
isSystemBuild,
});
@@ -309,13 +311,26 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
},
);
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatObjectMetadataMaps'],
},
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatApplicationMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'flatFieldMetadataMaps',
'flatApplicationMaps',
]);
const ownerFlatApplication = isDefined(applicationId)
? flatApplicationMaps.byId[applicationId]
: workspaceCustomFlatApplication;
if (!isDefined(ownerFlatApplication)) {
throw new FieldMetadataException(
`Could not find related application ${applicationId}`,
FieldMetadataExceptionCode.APPLICATION_NOT_FOUND,
);
}
const allTranspiledTranspilationInputs: Awaited<
ReturnType<typeof fromCreateFieldInputToFlatFieldMetadatasToCreate>
@@ -325,10 +340,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
allTranspiledTranspilationInputs.push(
await fromCreateFieldInputToFlatFieldMetadatasToCreate({
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
createFieldInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
flatApplication: ownerFlatApplication,
}),
);
}
@@ -38,6 +38,7 @@ export const fieldMetadataGraphqlApiExceptionHandler = (
case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS:
throw new ConflictError(error);
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
case FieldMetadataExceptionCode.APPLICATION_NOT_FOUND:
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
@@ -43,8 +43,6 @@ export const transformAgentEntityToFlatAgent = ({
__universal: {
universalIdentifier: agentEntity.universalIdentifier,
applicationUniversalIdentifier,
responseFormat: agentEntity.responseFormat,
modelConfiguration: agentEntity.modelConfiguration,
},
};
};
@@ -4,6 +4,7 @@ import { type Expect } from 'twenty-shared/testing';
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type WorkspaceMigrationActionType } from 'src/engine/metadata-modules/flat-entity/types/metadata-workspace-migration-action.type';
import { type UniversalSyncableFlatEntity } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import { type WorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/workspace-migration-action-common';
type ExpectedGenericFlatEntityInformation = {
@@ -13,6 +14,7 @@ type ExpectedGenericFlatEntityInformation = {
| WorkspaceMigrationAction[];
};
flatEntity: SyncableFlatEntity;
universalFlatEntity: UniversalSyncableFlatEntity;
};
type ExpectedGenericAllFlatEntityInformationByMetadataEngine = {
@@ -1,10 +1,12 @@
import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { type CommandMenuItemEntity } from 'src/engine/metadata-modules/command-menu-item/entities/command-menu-item.entity';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type';
import { type FlatCommandMenuItem } from 'src/engine/metadata-modules/flat-command-menu-item/types/flat-command-menu-item.type';
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type';
import { type FlatNavigationMenuItem } from 'src/engine/metadata-modules/flat-navigation-menu-item/types/flat-navigation-menu-item.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
@@ -18,10 +20,30 @@ import { type FlatViewFilterGroup } from 'src/engine/metadata-modules/flat-view-
import { type FlatViewFilter } from 'src/engine/metadata-modules/flat-view-filter/types/flat-view-filter.type';
import { type FlatViewGroup } from 'src/engine/metadata-modules/flat-view-group/types/flat-view-group.type';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type FlatWebhook } from 'src/engine/metadata-modules/flat-webhook/types/flat-webhook.type';
import { type FrontComponentEntity } from 'src/engine/metadata-modules/front-component/entities/front-component.entity';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { type NavigationMenuItemEntity } from 'src/engine/metadata-modules/navigation-menu-item/entities/navigation-menu-item.entity';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type PageLayoutEntity } from 'src/engine/metadata-modules/page-layout/entities/page-layout.entity';
import { type PageLayoutTabEntity } from 'src/engine/metadata-modules/page-layout-tab/entities/page-layout-tab.entity';
import { type PageLayoutWidgetEntity } from 'src/engine/metadata-modules/page-layout-widget/entities/page-layout-widget.entity';
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { type RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type';
import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { type RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
import { type RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
import { type SkillEntity } from 'src/engine/metadata-modules/skill/entities/skill.entity';
import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
import { type ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { type WebhookEntity } from 'src/engine/metadata-modules/webhook/entities/webhook.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
import {
type CreateAgentAction,
type DeleteAgentAction,
@@ -141,6 +163,10 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteFieldAction;
};
flatEntity: FlatFieldMetadata;
universalFlatEntity: UniversalFlatEntityFrom<
FieldMetadataEntity,
'fieldMetadata'
>;
entity: MetadataEntity<'fieldMetadata'>;
};
objectMetadata: {
@@ -150,6 +176,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteObjectAction;
};
flatEntity: FlatObjectMetadata;
universalFlatEntity: UniversalFlatEntityFrom<ObjectMetadataEntity>;
entity: MetadataEntity<'objectMetadata'>;
};
view: {
@@ -159,6 +186,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteViewAction;
};
flatEntity: FlatView;
universalFlatEntity: UniversalFlatEntityFrom<ViewEntity>;
entity: MetadataEntity<'view'>;
};
viewField: {
@@ -168,6 +196,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteViewFieldAction;
};
flatEntity: FlatViewField;
universalFlatEntity: UniversalFlatEntityFrom<ViewFieldEntity>;
entity: MetadataEntity<'viewField'>;
};
viewGroup: {
@@ -177,6 +206,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteViewGroupAction;
};
flatEntity: FlatViewGroup;
universalFlatEntity: UniversalFlatEntityFrom<ViewGroupEntity>;
entity: MetadataEntity<'viewGroup'>;
};
rowLevelPermissionPredicate: {
@@ -186,6 +216,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteRowLevelPermissionPredicateAction;
};
flatEntity: FlatRowLevelPermissionPredicate;
universalFlatEntity: UniversalFlatEntityFrom<RowLevelPermissionPredicateEntity>;
entity: MetadataEntity<'rowLevelPermissionPredicate'>;
};
rowLevelPermissionPredicateGroup: {
@@ -195,6 +226,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteRowLevelPermissionPredicateGroupAction;
};
flatEntity: FlatRowLevelPermissionPredicateGroup;
universalFlatEntity: UniversalFlatEntityFrom<RowLevelPermissionPredicateGroupEntity>;
entity: MetadataEntity<'rowLevelPermissionPredicateGroup'>;
};
viewFilterGroup: {
@@ -204,6 +236,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteViewFilterGroupAction;
};
flatEntity: FlatViewFilterGroup;
universalFlatEntity: UniversalFlatEntityFrom<ViewFilterGroupEntity>;
entity: MetadataEntity<'viewFilterGroup'>;
};
index: {
@@ -213,6 +246,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteIndexAction;
};
flatEntity: FlatIndexMetadata;
universalFlatEntity: UniversalFlatEntityFrom<IndexMetadataEntity>;
entity: MetadataEntity<'index'>;
};
logicFunction: {
@@ -222,6 +256,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteLogicFunctionAction;
};
flatEntity: FlatLogicFunction;
universalFlatEntity: UniversalFlatEntityFrom<LogicFunctionEntity>;
entity: MetadataEntity<'logicFunction'>;
};
viewFilter: {
@@ -231,6 +266,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteViewFilterAction;
};
flatEntity: FlatViewFilter;
universalFlatEntity: UniversalFlatEntityFrom<ViewFilterEntity>;
entity: MetadataEntity<'viewFilter'>;
};
role: {
@@ -240,6 +276,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteRoleAction;
};
flatEntity: FlatRole;
universalFlatEntity: UniversalFlatEntityFrom<RoleEntity>;
entity: MetadataEntity<'role'>;
};
roleTarget: {
@@ -249,6 +286,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteRoleTargetAction;
};
flatEntity: FlatRoleTarget;
universalFlatEntity: UniversalFlatEntityFrom<RoleTargetEntity>;
entity: MetadataEntity<'roleTarget'>;
};
agent: {
@@ -258,6 +296,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteAgentAction;
};
flatEntity: FlatAgent;
universalFlatEntity: UniversalFlatEntityFrom<AgentEntity>;
entity: MetadataEntity<'agent'>;
};
skill: {
@@ -267,6 +306,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteSkillAction;
};
flatEntity: FlatSkill;
universalFlatEntity: UniversalFlatEntityFrom<SkillEntity>;
entity: MetadataEntity<'skill'>;
};
commandMenuItem: {
@@ -276,6 +316,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteCommandMenuItemAction;
};
flatEntity: FlatCommandMenuItem;
universalFlatEntity: UniversalFlatEntityFrom<CommandMenuItemEntity>;
entity: MetadataEntity<'commandMenuItem'>;
};
navigationMenuItem: {
@@ -285,6 +326,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteNavigationMenuItemAction;
};
flatEntity: FlatNavigationMenuItem;
universalFlatEntity: UniversalFlatEntityFrom<NavigationMenuItemEntity>;
entity: NavigationMenuItemEntity;
};
pageLayout: {
@@ -294,6 +336,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeletePageLayoutAction;
};
flatEntity: FlatPageLayout;
universalFlatEntity: UniversalFlatEntityFrom<PageLayoutEntity>;
entity: MetadataEntity<'pageLayout'>;
};
pageLayoutWidget: {
@@ -303,6 +346,10 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeletePageLayoutWidgetAction;
};
flatEntity: FlatPageLayoutWidget;
universalFlatEntity: UniversalFlatEntityFrom<
PageLayoutWidgetEntity,
'pageLayoutWidget'
>;
entity: MetadataEntity<'pageLayoutWidget'>;
};
pageLayoutTab: {
@@ -312,6 +359,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeletePageLayoutTabAction;
};
flatEntity: FlatPageLayoutTab;
universalFlatEntity: UniversalFlatEntityFrom<PageLayoutTabEntity>;
entity: MetadataEntity<'pageLayoutTab'>;
};
frontComponent: {
@@ -321,6 +369,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteFrontComponentAction;
};
flatEntity: FlatFrontComponent;
universalFlatEntity: UniversalFlatEntityFrom<FrontComponentEntity>;
entity: MetadataEntity<'frontComponent'>;
};
webhook: {
@@ -330,6 +379,7 @@ export type AllFlatEntityTypesByMetadataName = {
delete: DeleteWebhookAction;
};
flatEntity: FlatWebhook;
universalFlatEntity: UniversalFlatEntityFrom<WebhookEntity>;
entity: MetadataEntity<'webhook'>;
};
};
@@ -0,0 +1,15 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
/**
* Currently under migration but aims to replace FlatEntity afterwards
*/
export type FlatEntityFromV2<
TEntity,
TMetadataName extends AllMetadataName | undefined = undefined,
TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom<
TEntity,
TMetadataName
>,
> = Omit<TInnerFlatEntity, '__universal'> & TInnerFlatEntity['__universal'];
@@ -16,6 +16,7 @@ export type SyncableFlatEntity = Omit<
export type FlatEntityFrom<
TEntity,
// Required to be passed for narrowed type
TMetadataName extends AllMetadataName | undefined = undefined,
> = Omit<
TEntity,
@@ -0,0 +1,6 @@
import { type AllMetadataName } from 'twenty-shared/metadata';
import { type AllFlatEntityTypesByMetadataName } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name';
export type MetadataUniversalFlatEntity<T extends AllMetadataName> =
AllFlatEntityTypesByMetadataName[T]['universalFlatEntity'];
@@ -45,8 +45,16 @@ export const getFlatFieldMetadataMock = <T extends FieldMetadataType>(
applicationId: faker.string.uuid(),
relationTargetFieldMetadataId: null,
relationTargetObjectMetadataId: null,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
__universal: {} as any,
applicationUniversalIdentifier: faker.string.uuid(),
objectMetadataUniversalIdentifier: faker.string.uuid(),
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
...overrides,
};
};
@@ -65,6 +65,15 @@ export const getRelationTargetFlatFieldMetadataMock = ({
defaultValue: null,
options: null,
applicationId: faker.string.uuid(),
__universal: undefined,
applicationUniversalIdentifier: faker.string.uuid(),
objectMetadataUniversalIdentifier: faker.string.uuid(),
relationTargetObjectMetadataUniversalIdentifier: faker.string.uuid(),
relationTargetFieldMetadataUniversalIdentifier: faker.string.uuid(),
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: settings,
};
};
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test suite Failure cases should fail when morphRelationsCreationPayload has different relation types 1`] = `
{
@@ -117,7 +117,9 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"flatFieldMetadatas": [
{
"applicationId": Any<String>,
"applicationUniversalIdentifier": Any<String>,
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": Any<String>,
"defaultValue": null,
"description": "new field description",
@@ -131,14 +133,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "newFieldLabel",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": Any<String>,
"name": "newFieldPets",
"objectMetadataId": Any<String>,
"objectMetadataUniversalIdentifier": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
@@ -146,14 +153,21 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"universalSettings": {
"relationType": "ONE_TO_MANY",
},
"updatedAt": Any<String>,
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": Any<String>,
},
{
"applicationId": Any<String>,
"applicationUniversalIdentifier": Any<String>,
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": Any<String>,
"defaultValue": null,
"description": "new field description",
@@ -167,14 +181,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "Pet",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "pet",
"objectMetadataId": Any<String>,
"objectMetadataUniversalIdentifier": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
"settings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
@@ -184,14 +203,23 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"universalSettings": {
"joinColumnName": "petId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"updatedAt": Any<String>,
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": Any<String>,
},
{
"applicationId": Any<String>,
"applicationUniversalIdentifier": Any<String>,
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": Any<String>,
"defaultValue": null,
"description": "new field description",
@@ -205,14 +233,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "newFieldLabel",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": Any<String>,
"name": "newFieldCompanies",
"objectMetadataId": Any<String>,
"objectMetadataUniversalIdentifier": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
"settings": {
"relationType": "ONE_TO_MANY",
},
@@ -220,14 +253,21 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"standardOverrides": null,
"type": "MORPH_RELATION",
"universalIdentifier": Any<String>,
"universalSettings": {
"relationType": "ONE_TO_MANY",
},
"updatedAt": Any<String>,
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": Any<String>,
},
{
"applicationId": Any<String>,
"applicationUniversalIdentifier": Any<String>,
"calendarViewIds": [],
"calendarViewUniversalIdentifiers": [],
"createdAt": Any<String>,
"defaultValue": null,
"description": "new field description",
@@ -241,14 +281,19 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"isUIReadOnly": false,
"isUnique": false,
"kanbanAggregateOperationViewIds": [],
"kanbanAggregateOperationViewUniversalIdentifiers": [],
"label": "Company",
"mainGroupByFieldMetadataViewIds": [],
"mainGroupByFieldMetadataViewUniversalIdentifiers": [],
"morphId": null,
"name": "company",
"objectMetadataId": Any<String>,
"objectMetadataUniversalIdentifier": Any<String>,
"options": null,
"relationTargetFieldMetadataId": Any<String>,
"relationTargetFieldMetadataUniversalIdentifier": Any<String>,
"relationTargetObjectMetadataId": Any<String>,
"relationTargetObjectMetadataUniversalIdentifier": Any<String>,
"settings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
@@ -258,9 +303,16 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"standardOverrides": null,
"type": "RELATION",
"universalIdentifier": Any<String>,
"universalSettings": {
"joinColumnName": "companyId",
"onDelete": "SET_NULL",
"relationType": "MANY_TO_ONE",
},
"updatedAt": Any<String>,
"viewFieldIds": [],
"viewFieldUniversalIdentifiers": [],
"viewFilterIds": [],
"viewFilterUniversalIdentifiers": [],
"workspaceId": Any<String>,
},
],
@@ -5,8 +5,11 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import {
fromCreateFieldInputToFlatFieldMetadatasToCreate,
type FromCreateFieldInputToFlatObjectMetadataArgs,
@@ -16,6 +19,24 @@ import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/fla
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
import { ROCKET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/rocket-flat-object.mock';
const MOCK_FLAT_APPLICATION: FlatApplication = {
id: '20202020-81ee-42da-a281-668632f32fe7',
universalIdentifier: '20202020-81ee-42da-a281-668632f32fe7',
workspaceId: 'mock-workspace-id',
name: 'Workspace Custom Application',
description: null,
version: null,
sourceType: 'local',
sourcePath: '',
logicFunctionLayerId: null,
defaultLogicFunctionRoleId: null,
defaultLogicFunctionRole: null,
canBeUninstalled: false,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
const flatObjectMetadataMaps = [
COMPANY_FLAT_OBJECT_MOCK,
ROCKET_FLAT_OBJECT_MOCK,
@@ -27,6 +48,9 @@ const flatObjectMetadataMaps = [
});
}, createEmptyFlatEntityMaps());
const emptyFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata> =
createEmptyFlatEntityMaps();
type TestCase = EachTestingContext<{
input: FromCreateFieldInputToFlatObjectMetadataArgs;
expected: 'success' | 'fail';
@@ -42,8 +66,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should create morph relation field metadata with valid input on rocket object to pet object',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -67,6 +90,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'success',
},
@@ -102,8 +126,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is missing',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -114,6 +137,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -122,8 +146,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is empty array',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -134,6 +157,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -143,8 +167,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has different relation types',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -168,6 +191,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -177,8 +201,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has several references to same object metadata',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -202,6 +225,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -211,8 +235,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has invalid relation payload',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -229,6 +252,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -237,8 +261,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when target object metadata is not found',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -256,6 +279,7 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
},
workspaceId: mockWorkspaceId,
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatFieldMetadataMaps: emptyFlatFieldMetadataMaps,
},
expected: 'fail',
},
@@ -71,3 +71,48 @@ type FlatTransformationAssertions = [
Expect<HasAllProperties<FlatFieldMetadata, DatePropertiesCastToString>>,
Expect<HasAllProperties<FlatFieldMetadata, OneToManyRelationIdArrays>>,
];
// Any narrowed flatFieldMetadata type should be assignable to non narrowed flatFieldMetadata
type AbstractFlatFieldMetadata = FlatFieldMetadata<FieldMetadataType>;
const _assertion: Record<string, AbstractFlatFieldMetadata> = {
// Primitive types
uuid: {} as FlatFieldMetadata<FieldMetadataType.UUID>,
text: {} as FlatFieldMetadata<FieldMetadataType.TEXT>,
number: {} as FlatFieldMetadata<FieldMetadataType.NUMBER>,
boolean: {} as FlatFieldMetadata<FieldMetadataType.BOOLEAN>,
numeric: {} as FlatFieldMetadata<FieldMetadataType.NUMERIC>,
position: {} as FlatFieldMetadata<FieldMetadataType.POSITION>,
// Date types
date: {} as FlatFieldMetadata<FieldMetadataType.DATE>,
dateTime: {} as FlatFieldMetadata<FieldMetadataType.DATE_TIME>,
// Complex types
currency: {} as FlatFieldMetadata<FieldMetadataType.CURRENCY>,
fullName: {} as FlatFieldMetadata<FieldMetadataType.FULL_NAME>,
address: {} as FlatFieldMetadata<FieldMetadataType.ADDRESS>,
links: {} as FlatFieldMetadata<FieldMetadataType.LINKS>,
emails: {} as FlatFieldMetadata<FieldMetadataType.EMAILS>,
phones: {} as FlatFieldMetadata<FieldMetadataType.PHONES>,
actor: {} as FlatFieldMetadata<FieldMetadataType.ACTOR>,
// Select types
rating: {} as FlatFieldMetadata<FieldMetadataType.RATING>,
select: {} as FlatFieldMetadata<FieldMetadataType.SELECT>,
multiSelect: {} as FlatFieldMetadata<FieldMetadataType.MULTI_SELECT>,
// JSON/Array types
rawJson: {} as FlatFieldMetadata<FieldMetadataType.RAW_JSON>,
array: {} as FlatFieldMetadata<FieldMetadataType.ARRAY>,
richText: {} as FlatFieldMetadata<FieldMetadataType.RICH_TEXT>,
richTextV2: {} as FlatFieldMetadata<FieldMetadataType.RICH_TEXT_V2>,
// Relation types
relation: {} as FlatFieldMetadata<FieldMetadataType.RELATION>,
morphRelation: {} as FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>,
// Special types
files: {} as FlatFieldMetadata<FieldMetadataType.FILES>,
tsVector: {} as FlatFieldMetadata<FieldMetadataType.TS_VECTOR>,
};
@@ -1,10 +1,10 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type';
export type FlatFieldMetadata<T extends FieldMetadataType = FieldMetadataType> =
FlatEntityFrom<
FlatEntityFromV2<
Omit<
FieldMetadataEntity<T>,
'relationTargetFieldMetadata' | 'relationTargetObjectMetadata'
@@ -6,6 +6,7 @@ import {
RelationType,
} from 'twenty-shared/types';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import {
generateMorphOrRelationFlatFieldMetadataPair,
type SourceTargetMorphOrRelationFlatFieldAndFlatIndex,
@@ -13,6 +14,24 @@ import {
import { COMPANY_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/company-flat-object.mock';
import { PET_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/pet-flat-object.mock';
const MOCK_FLAT_APPLICATION: FlatApplication = {
id: '20202020-81ee-42da-a281-668632f32fe7',
universalIdentifier: '20202020-81ee-42da-a281-668632f32fe7',
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
name: 'Workspace Custom Application',
description: null,
version: null,
sourceType: 'local',
sourcePath: '',
logicFunctionLayerId: null,
defaultLogicFunctionRoleId: null,
defaultLogicFunctionRole: null,
canBeUninstalled: false,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
type GenerateMorphOrRelationFlatFieldMetadataPairTestInput = Parameters<
typeof generateMorphOrRelationFlatFieldMetadataPair
>[0];
@@ -28,8 +47,6 @@ type TestCase = EachTestingContext<{
describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () => {
const mockWorkspaceId = '20202020-1c25-4d02-bf25-6aeccf7ea419';
const mockWorkspaceCustomApplicationId =
'20202020-81ee-42da-a281-668632f32fe7';
describe('Success cases', () => {
const testCases: TestCase[] = [
@@ -43,7 +60,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -79,7 +96,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -115,7 +132,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'petId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
createFieldInput: {
name: 'pets',
label: 'Pets',
@@ -151,7 +168,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -188,7 +205,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -225,7 +242,7 @@ describe('generate Morph Or Relation Flat Field Metadata Pair test suite', () =>
targetFlatFieldMetadataType: FieldMetadataType.MORPH_RELATION,
sourceFlatObjectMetadataJoinColumnName: 'targetPetId',
workspaceId: mockWorkspaceId,
workspaceCustomApplicationId: mockWorkspaceCustomApplicationId,
flatApplication: MOCK_FLAT_APPLICATION,
morphId: '20202020-9a2b-4c3d-a4e5-f6a7b8c9d0e1',
createFieldInput: {
name: 'targetPet',
@@ -4,17 +4,19 @@ import {
} from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { getMorphNameFromMorphFieldMetadataName } from 'src/engine/metadata-modules/flat-object-metadata/utils/get-morph-name-from-morph-field-metadata-name.util';
type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
morphRelationsUpdatePayload?: RelationUpdatePayload[];
fieldMetadataToUpdate: FlatFieldMetadata<FieldMetadataType.MORPH_RELATION>;
@@ -22,7 +24,7 @@ type ComputeFlatFieldToUpdateFromMorphRelationUpdatePayloadArgs = {
};
export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
workspaceCustomApplicationId,
flatApplication,
flatFieldMetadataMaps,
morphRelationsUpdatePayload,
fieldMetadataToUpdate,
@@ -68,6 +70,13 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
const commonTargetFieldName = initialTargetFieldMetadata.name;
const commonObjectMetadataId = initialTargetFieldMetadata.objectMetadataId;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
fieldMetadataToUpdate.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? flatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
morphRelationsUpdatePayload.forEach((morphRelationUpdatePayload) => {
const { targetObjectMetadataId } = morphRelationUpdatePayload;
@@ -101,13 +110,14 @@ export const computeFlatFieldToUpdateFromMorphRelationUpdatePayload = ({
targetFlatObjectMetadata: newTargetObjectMetadata,
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId: fieldMetadataToUpdate.workspaceId,
workspaceCustomApplicationId,
flatApplication,
sourceFlatObjectMetadataJoinColumnName:
computeMorphOrRelationFieldJoinColumnName({
name: computedMorphName,
}),
morphId: fieldMetadataToUpdate.morphId,
targetFieldName: commonTargetFieldName,
junctionTargetFlatFieldMetadata,
});
flatFieldMetadatasToCreate.push(...flatFieldMetadatas);
@@ -5,6 +5,7 @@ type JunctionTargetSettings = {
junctionTargetFieldId?: string;
};
// TODO refactor using either type predicate or FieldMetadataType discriminating union
// Extracts junction target settings from untyped settings input
// This function handles the boundary where settings come from external API input
export const extractJunctionTargetSettingsFromSettings = (
@@ -10,6 +10,7 @@ import {
} from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { generateRatingOptions } from 'src/engine/metadata-modules/field-metadata/utils/generate-rating-optionts.util';
@@ -25,14 +26,15 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m
export type FromCreateFieldInputToFlatObjectMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
flatApplication: FlatApplication;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps' | 'flatFieldMetadataMaps'>;
export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput: rawCreateFieldInput,
workspaceId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
workspaceCustomApplicationId,
flatFieldMetadataMaps: existingFlatFieldMetadataMaps,
flatApplication,
}: FromCreateFieldInputToFlatObjectMetadataArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -77,7 +79,9 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput,
workspaceId,
fieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
parentFlatObjectMetadata.universalIdentifier,
});
switch (createFieldInput.type) {
@@ -88,21 +92,23 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
type: createFieldInput.type,
},
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
});
}
case FieldMetadataType.RELATION: {
return await fromRelationCreateFieldInputToFlatFieldMetadatas({
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
createFieldInput: {
...createFieldInput,
type: createFieldInput.type,
},
workspaceId,
workspaceCustomApplicationId,
flatApplication,
});
}
case FieldMetadataType.RATING: {
@@ -106,33 +106,27 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = ({
[],
viewFieldIds: fieldMetadataEntity.viewFields.map(({ id }) => id),
viewFilterIds: fieldMetadataEntity.viewFilters.map(({ id }) => id),
__universal: {
universalIdentifier: fieldMetadataWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier,
relationTargetFieldMetadataUniversalIdentifier,
viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map(
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier,
relationTargetFieldMetadataUniversalIdentifier,
viewFieldUniversalIdentifiers: fieldMetadataEntity.viewFields.map(
({ universalIdentifier }) => universalIdentifier,
),
viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map(
({ universalIdentifier }) => universalIdentifier,
),
kanbanAggregateOperationViewUniversalIdentifiers:
fieldMetadataEntity.kanbanAggregateOperationViews.map(
({ universalIdentifier }) => universalIdentifier,
),
viewFilterUniversalIdentifiers: fieldMetadataEntity.viewFilters.map(
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
({ universalIdentifier }) => universalIdentifier,
),
mainGroupByFieldMetadataViewUniversalIdentifiers:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
({ universalIdentifier }) => universalIdentifier,
),
kanbanAggregateOperationViewUniversalIdentifiers:
fieldMetadataEntity.kanbanAggregateOperationViews.map(
({ universalIdentifier }) => universalIdentifier,
),
calendarViewUniversalIdentifiers: fieldMetadataEntity.calendarViews.map(
({ universalIdentifier }) => universalIdentifier,
),
mainGroupByFieldMetadataViewUniversalIdentifiers:
fieldMetadataEntity.mainGroupByFieldMetadataViews?.map(
({ universalIdentifier }) => universalIdentifier,
) ?? [],
settings: settingsWithUniversalIdentifiers,
defaultValue: fieldMetadataWithoutRelations.defaultValue,
options: fieldMetadataWithoutRelations.options,
standardOverrides: fieldMetadataWithoutRelations.standardOverrides,
},
) ?? [],
universalSettings: settingsWithUniversalIdentifiers,
};
};
@@ -3,12 +3,14 @@ import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateMorphOrRelationFlatFieldMetadataPair } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-morph-or-relation-flat-field-metadata-pair.util';
import { validateMorphRelationCreationPayload } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-morph-relation-creation-payload.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -19,16 +21,18 @@ type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
type: FieldMetadataType.MORPH_RELATION;
};
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
existingFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -66,6 +70,13 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
return morphRelationCreationPayloadValidation;
}
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
const morphRelationCreationPayload =
morphRelationCreationPayloadValidation.result;
const morphId = v4();
@@ -95,7 +106,8 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId,
morphId,
workspaceCustomApplicationId,
flatApplication,
junctionTargetFlatFieldMetadata,
});
return {
@@ -2,11 +2,14 @@ import { msg } from '@lingui/core/macro';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import {
generateMorphOrRelationFlatFieldMetadataPair,
type SourceTargetMorphOrRelationFlatFieldAndFlatIndex,
@@ -19,16 +22,18 @@ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
type: FieldMetadataType.RELATION;
};
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
existingFlatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
existingFlatObjectMetadataMaps,
existingFlatFieldMetadataMaps,
sourceFlatObjectMetadata,
createFieldInput,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<SourceTargetMorphOrRelationFlatFieldAndFlatIndex>
> => {
@@ -59,6 +64,13 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
const { relationCreationPayload, targetFlatObjectMetadata } =
relationValidationResult.result;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const junctionTargetFlatFieldMetadata = isDefined(junctionTargetFieldId)
? existingFlatFieldMetadataMaps.byId[junctionTargetFieldId]
: undefined;
const generateResult = generateMorphOrRelationFlatFieldMetadataPair({
createFieldInput: {
...createFieldInput,
@@ -72,7 +84,8 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatObjectMetadata,
targetFlatFieldMetadataType: FieldMetadataType.RELATION,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
junctionTargetFlatFieldMetadata,
});
return {
@@ -5,6 +5,7 @@ import {
isDefined,
} from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
@@ -23,7 +24,7 @@ import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-fiel
type FromUpdateFieldInputToFlatFieldMetadataArgs = {
updateFieldInput: UpdateFieldInput;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
isSystemBuild: boolean;
} & Pick<
AllFlatEntityMaps,
@@ -41,7 +42,7 @@ type FlatFieldMetadataAndIndexToUpdate = {
flatFieldMetadatasToCreate: FlatFieldMetadata[];
} & FlatFieldMetadataUpdateSideEffects;
export const fromUpdateFieldInputToFlatFieldMetadata = ({
workspaceCustomApplicationId,
flatApplication,
flatIndexMaps,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatFieldMetadataMaps,
@@ -109,7 +110,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
FieldMetadataType.MORPH_RELATION,
)
? computeFlatFieldToUpdateFromMorphRelationUpdatePayload({
workspaceCustomApplicationId,
flatApplication,
morphRelationsUpdatePayload:
rawUpdateFieldInput?.morphRelationsUpdatePayload,
flatFieldMetadataMaps: flatFieldMetadataMaps,
@@ -149,7 +150,7 @@ export const fromUpdateFieldInputToFlatFieldMetadata = ({
toFlatFieldMetadata,
flatViewMaps,
flatViewFieldMaps,
workspaceCustomApplicationId,
flatApplication,
});
if (sideEffectResult.status === 'fail') {
@@ -6,11 +6,11 @@ import {
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type MorphOrRelationFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/types/morph-or-relation-field-metadata-type.type';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { extractJunctionTargetSettingsFromSettings } from 'src/engine/metadata-modules/flat-field-metadata/utils/extract-junction-target-settings-from-settings.util';
import { generateIndexForFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/generate-index-for-flat-field-metadata.util';
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -22,23 +22,39 @@ type ComputeFieldMetadataRelationSettingsForRelationTypeArgs = {
relationType: RelationType;
joinColumnName: string;
junctionTargetFieldId?: string;
junctionTargetFieldUniversalIdentifier?: string;
};
const computeFieldMetadataRelationSettingsForRelationType = ({
relationType,
joinColumnName,
junctionTargetFieldId,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs) => {
junctionTargetFieldUniversalIdentifier,
}: ComputeFieldMetadataRelationSettingsForRelationTypeArgs): Pick<
FlatFieldMetadata<MorphOrRelationFieldMetadataType>,
'settings' | 'universalSettings'
> => {
if (relationType === RelationType.MANY_TO_ONE) {
return {
const settings = {
relationType: RelationType.MANY_TO_ONE,
onDelete: RelationOnDeleteAction.SET_NULL,
joinColumnName,
};
return { settings, universalSettings: settings };
}
return {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldId && { junctionTargetFieldId }),
settings: {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldId && { junctionTargetFieldId }),
},
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
...(junctionTargetFieldUniversalIdentifier && {
junctionTargetFieldUniversalIdentifier,
}),
},
};
};
@@ -53,8 +69,9 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = {
> & { type: MorphOrRelationFieldMetadataType };
workspaceId: string;
morphId?: string | null;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
targetFieldName?: string;
junctionTargetFlatFieldMetadata?: FlatFieldMetadata;
};
export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = {
@@ -68,27 +85,30 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
targetFlatObjectMetadata,
targetFlatFieldMetadataType,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
sourceFlatObjectMetadataJoinColumnName,
morphId = null,
targetFieldName,
junctionTargetFlatFieldMetadata,
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => {
const sourceFlatFieldMetadataType = createFieldInput.type;
const { relationCreationPayload } = createFieldInput;
const { junctionTargetFieldId } = extractJunctionTargetSettingsFromSettings(
createFieldInput.settings,
);
const sourceFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: sourceFlatObjectMetadataJoinColumnName,
relationType: relationCreationPayload.type,
junctionTargetFieldId,
});
const {
settings: sourceFlatFieldMetadataSettings,
universalSettings: sourceFlatFieldMetadataUniversalSettings,
} = computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: sourceFlatObjectMetadataJoinColumnName,
relationType: relationCreationPayload.type,
junctionTargetFieldId: junctionTargetFlatFieldMetadata?.id,
junctionTargetFieldUniversalIdentifier:
junctionTargetFlatFieldMetadata?.universalIdentifier,
});
const targetRelationTargetFieldMetadataId = v4();
const sourceRelationTargetFieldMetadataId = v4();
const sourceFieldUniversalIdentifier = v4();
const targetFieldUniversalIdentifier = v4();
const defaultDescriptionFromField =
buildDescriptionForRelationFieldMetadataOnFromField({
@@ -105,10 +125,15 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
'flatRelationTargetFieldMetadata'
> = {
...getDefaultFlatFieldMetadata({
createFieldInput,
createFieldInput: {
...createFieldInput,
universalIdentifier: sourceFieldUniversalIdentifier,
},
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
sourceFlatObjectMetadata.universalIdentifier,
}),
morphId:
sourceFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION
@@ -121,9 +146,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput.description ?? defaultDescriptionFromField.description,
defaultValue: null,
settings: sourceFlatFieldMetadataSettings,
universalSettings: sourceFlatFieldMetadataUniversalSettings,
options: null,
relationTargetFieldMetadataId: targetRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: targetFlatObjectMetadata.id,
relationTargetObjectMetadataUniversalIdentifier:
targetFlatObjectMetadata.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
targetFieldUniversalIdentifier,
};
const targetCreateFieldInput: CreateFieldInput = {
@@ -141,25 +171,32 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
workspaceId,
isSystem: createFieldInput.isSystem ?? false,
};
const targetFlatFieldMetadataSettings =
computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: computeMorphOrRelationFieldJoinColumnName({
name: targetCreateFieldInput.name,
}),
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const {
settings: targetFlatFieldMetadataSettings,
universalSettings: targetFlatFieldMetadataUniversalSettings,
} = computeFieldMetadataRelationSettingsForRelationType({
joinColumnName: computeMorphOrRelationFieldJoinColumnName({
name: targetCreateFieldInput.name,
}),
relationType:
relationCreationPayload.type === RelationType.ONE_TO_MANY
? RelationType.MANY_TO_ONE
: RelationType.ONE_TO_MANY,
});
const targetFlatFieldMetadata: FlatFieldMetadata<
typeof targetFlatFieldMetadataType
> = {
...getDefaultFlatFieldMetadata({
createFieldInput: targetCreateFieldInput,
createFieldInput: {
...targetCreateFieldInput,
universalIdentifier: targetFieldUniversalIdentifier,
},
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier:
targetFlatObjectMetadata.universalIdentifier,
}),
morphId:
targetFlatFieldMetadataType === FieldMetadataType.MORPH_RELATION
@@ -168,9 +205,14 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
type: targetFlatFieldMetadataType,
defaultValue: null,
settings: targetFlatFieldMetadataSettings,
universalSettings: targetFlatFieldMetadataUniversalSettings,
options: null,
relationTargetFieldMetadataId: sourceRelationTargetFieldMetadataId,
relationTargetObjectMetadataId: sourceFlatObjectMetadata.id,
relationTargetObjectMetadataUniversalIdentifier:
sourceFlatObjectMetadata.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier:
sourceFieldUniversalIdentifier,
};
const indexMetadata: FlatIndexMetadata = generateIndexForFlatFieldMetadata({
@@ -1,6 +1,7 @@
import { extractAndSanitizeObjectStringFields } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value';
import { generateNullable } from 'src/engine/metadata-modules/field-metadata/utils/generate-nullable';
@@ -10,13 +11,15 @@ type GetDefaultFlatFieldMetadataArgs = {
fieldMetadataId: string;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
objectMetadataUniversalIdentifier: string;
};
export const getDefaultFlatFieldMetadata = ({
createFieldInput,
fieldMetadataId,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
objectMetadataUniversalIdentifier,
}: GetDefaultFlatFieldMetadataArgs) => {
const { defaultValue, settings } = extractAndSanitizeObjectStringFields(
createFieldInput,
@@ -58,9 +61,18 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
applicationId:
createFieldInput.applicationId ?? workspaceCustomApplicationId,
applicationId: flatApplication.id,
viewFilterIds: [],
kanbanAggregateOperationViewIds: [],
applicationUniversalIdentifier: flatApplication.universalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
} as const satisfies FlatFieldMetadata;
};
@@ -1,6 +1,7 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
@@ -41,7 +42,7 @@ type HandleFlatFieldMetadataUpdateSideEffectArgs = FromTo<
| 'flatViewMaps'
| 'flatViewFieldMaps'
> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
export const FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS: FlatFieldMetadataUpdateSideEffects =
@@ -70,7 +71,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatViewGroupMaps,
flatViewMaps,
flatViewFieldMaps,
workspaceCustomApplicationId,
flatApplication,
}: HandleFlatFieldMetadataUpdateSideEffectArgs): FieldInputTranspilationResult<FlatFieldMetadataUpdateSideEffects> => {
const sideEffectResult = structuredClone(
FLAT_FIELD_METADATA_UPDATE_EMPTY_SIDE_EFFECTS,
@@ -130,7 +131,7 @@ export const handleFlatFieldMetadataUpdateSideEffect = ({
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
});
if (indexChangesSideEffectResult.status === 'fail') {
@@ -2,6 +2,7 @@ import { msg } from '@lingui/core/macro';
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -28,7 +29,7 @@ type FromToFlatFieldMetadataAndFlatEntityMaps = FromTo<
AllFlatEntityMaps,
'flatObjectMetadataMaps' | 'flatFieldMetadataMaps' | 'flatIndexMaps'
> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
const FIELD_METADATA_UPDATE_INDEX_SIDE_EFFECT: FieldMetadataUpdateIndexSideEffect =
{
@@ -43,7 +44,7 @@ export const handleIndexChangesDuringFieldUpdate = ({
flatIndexMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
}: FromToFlatFieldMetadataAndFlatEntityMaps): FieldInputTranspilationResult<FieldMetadataUpdateIndexSideEffect> => {
if (
!hasIndexRelevantChanges({
@@ -81,7 +82,7 @@ export const handleIndexChangesDuringFieldUpdate = ({
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
});
};
@@ -127,13 +128,13 @@ const handleExistingIndexes = ({
relatedIndexes,
flatObjectMetadata,
flatFieldMetadataMaps,
workspaceCustomApplicationId,
flatApplication,
}: {
relatedIndexes: FlatIndexMetadata[];
flatObjectMetadata: FlatObjectMetadata;
flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps'];
} & FromTo<FlatFieldMetadata, 'flatFieldMetadata'> & {
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
}): FieldInputTranspilationResult<FieldMetadataUpdateIndexSideEffect> => {
if (
toFlatFieldMetadata.isUnique === false &&
@@ -155,7 +156,7 @@ const handleExistingIndexes = ({
if (
isDefined(uniqueIndexToDelete) &&
((isDefined(uniqueIndexToDelete.applicationId) &&
uniqueIndexToDelete.applicationId !== workspaceCustomApplicationId) ||
uniqueIndexToDelete.applicationId !== flatApplication.id) ||
!uniqueIndexToDelete.isCustom)
) {
return {
@@ -10,6 +10,11 @@ export const getFlatObjectMetadataMock = (
overrides: FlatObjectMetadataOverrides,
): FlatObjectMetadata => {
const createdAt = '2024-01-01T00:00:00.000Z';
const applicationId = overrides.applicationId ?? faker.string.uuid();
const labelIdentifierFieldMetadataId =
overrides.labelIdentifierFieldMetadataId ?? faker.string.uuid();
const imageIdentifierFieldMetadataId =
overrides.imageIdentifierFieldMetadataId ?? faker.string.uuid();
return {
viewIds: [],
@@ -18,7 +23,7 @@ export const getFlatObjectMetadataMock = (
description: 'default flat object metadata description',
icon: 'icon',
id: faker.string.uuid(),
imageIdentifierFieldMetadataId: faker.string.uuid(),
imageIdentifierFieldMetadataId,
isActive: true,
isAuditLogged: true,
isCustom: true,
@@ -27,20 +32,28 @@ export const getFlatObjectMetadataMock = (
isSearchable: true,
isSystem: false,
isUIReadOnly: false,
labelIdentifierFieldMetadataId: faker.string.uuid(),
labelIdentifierFieldMetadataId,
labelPlural: 'default flat object metadata label plural',
labelSingular: 'default flat object metadata label singular',
namePlural: 'defaultflatObjectMetadataNamePlural',
nameSingular: 'defaultflatObjectMetadataNameSingular',
shortcut: 'shortcut',
standardId: null,
applicationId: faker.string.uuid(),
applicationId,
standardOverrides: null,
targetTableName: '',
workspaceId: faker.string.uuid(),
createdAt,
updatedAt: createdAt,
duplicateCriteria: null,
applicationUniversalIdentifier: applicationId,
fieldUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
labelIdentifierFieldMetadataUniversalIdentifier:
labelIdentifierFieldMetadataId,
imageIdentifierFieldMetadataUniversalIdentifier:
imageIdentifierFieldMetadataId,
...overrides,
};
};
@@ -1,13 +1,12 @@
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type FlatEntityFromV2 } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from-v2.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
type BaseFlatObjectMetadata = FlatEntityFrom<
type BaseFlatObjectMetadata = FlatEntityFromV2<
Omit<ObjectMetadataEntity, 'targetRelationFields' | 'dataSourceId'>
>;
export type FlatObjectMetadata = Omit<BaseFlatObjectMetadata, '__universal'> & {
__universal?: BaseFlatObjectMetadata['__universal'] & {
// TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
imageIdentifierFieldMetadataUniversalIdentifier: string | null;
};
export type FlatObjectMetadata = BaseFlatObjectMetadata & {
// NOTE: below fields are not reflected on the final UniversalFlatEntity either they should we should define a common source
// TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved
labelIdentifierFieldMetadataUniversalIdentifier: string | null;
imageIdentifierFieldMetadataUniversalIdentifier: string | null;
};
@@ -6,6 +6,7 @@ import { v4 } from 'uuid';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
@@ -19,14 +20,14 @@ type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs
{
createObjectInput: CreateObjectInput;
workspaceId: string;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
existingFeatureFlagsMap: FeatureFlagMap;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate =
({
createObjectInput: rawCreateObjectInput,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
existingFeatureFlagsMap,
}: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): {
@@ -50,11 +51,14 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
);
const objectMetadataId = v4();
const universalIdentifier = createObjectInput.universalIdentifier ?? v4();
const defaultFlatFieldForCustomObjectMaps =
buildDefaultFlatFieldMetadatasForCustomObject({
flatObjectMetadata: {
id: objectMetadataId,
applicationId: workspaceCustomApplicationId,
applicationId: flatApplication.id,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
universalIdentifier,
},
workspaceId,
skipNameField: createObjectInput.skipNameField,
@@ -62,11 +66,12 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
const createdAt = new Date().toISOString();
// Use nameField.id if it exists, otherwise use idField.id (for junction tables without name)
const nameField = defaultFlatFieldForCustomObjectMaps.fields.nameField as
| FlatFieldMetadata
| undefined;
const nameField = defaultFlatFieldForCustomObjectMaps.fields.nameField;
const labelIdentifierFieldMetadataId =
nameField?.id ?? defaultFlatFieldForCustomObjectMaps.fields.idField.id;
const labelIdentifierFieldMetadataUniversalIdentifier =
nameField?.universalIdentifier ??
defaultFlatFieldForCustomObjectMaps.fields.idField.universalIdentifier;
const flatObjectMetadataToCreate: FlatObjectMetadata = {
fieldIds: [],
@@ -95,10 +100,16 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
shortcut: createObjectInput.shortcut ?? null,
standardId: createObjectInput.standardId ?? null,
standardOverrides: null,
applicationId: workspaceCustomApplicationId,
universalIdentifier: createObjectInput.universalIdentifier ?? v4(),
applicationId: flatApplication.id,
universalIdentifier,
targetTableName: 'DEPRECATED',
workspaceId,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
fieldUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
labelIdentifierFieldMetadataUniversalIdentifier,
imageIdentifierFieldMetadataUniversalIdentifier: null,
};
const {
@@ -108,7 +119,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata: flatObjectMetadataToCreate,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
existingFeatureFlagsMap,
});
@@ -76,24 +76,17 @@ export const fromObjectMetadataEntityToFlatObjectMetadata = ({
viewIds: objectMetadataEntity.views.map(({ id }) => id),
indexMetadataIds: objectMetadataEntity.indexMetadatas.map(({ id }) => id),
fieldIds: objectMetadataEntity.fields.map(({ id }) => id),
__universal: {
universalIdentifier:
objectMetadataEntityWithoutRelations.universalIdentifier,
applicationUniversalIdentifier,
labelIdentifierFieldMetadataUniversalIdentifier,
imageIdentifierFieldMetadataUniversalIdentifier,
fieldUniversalIdentifiers: objectMetadataEntity.fields.map(
({ universalIdentifier }) => universalIdentifier,
),
indexMetadataUniversalIdentifiers:
objectMetadataEntity.indexMetadatas.map(
({ universalIdentifier }) => universalIdentifier,
),
viewUniversalIdentifiers: objectMetadataEntity.views.map(
({ universalIdentifier }) => universalIdentifier,
),
standardOverrides: objectMetadataEntityWithoutRelations.standardOverrides,
duplicateCriteria: objectMetadataEntityWithoutRelations.duplicateCriteria,
},
applicationUniversalIdentifier,
labelIdentifierFieldMetadataUniversalIdentifier,
imageIdentifierFieldMetadataUniversalIdentifier,
fieldUniversalIdentifiers: objectMetadataEntity.fields.map(
({ universalIdentifier }) => universalIdentifier,
),
indexMetadataUniversalIdentifiers: objectMetadataEntity.indexMetadatas.map(
({ universalIdentifier }) => universalIdentifier,
),
viewUniversalIdentifiers: objectMetadataEntity.views.map(
({ universalIdentifier }) => universalIdentifier,
),
};
};
@@ -8,7 +8,7 @@ type PageLayoutWidgetConfiguration = PageLayoutWidgetEntity['configuration'];
type UniversalPageLayoutWidgetConfiguration = NonNullable<
FlatPageLayoutWidget['__universal']
>['configuration'];
>['universalConfiguration'];
// Field metadata IDs in widget configurations don't need to reference existing entities
const getFieldMetadataUniversalIdentifier = ({
@@ -85,10 +85,7 @@ export const fromPageLayoutWidgetEntityToFlatPageLayoutWidget = ({
applicationUniversalIdentifier,
pageLayoutTabUniversalIdentifier,
objectMetadataUniversalIdentifier,
gridPosition: pageLayoutWidgetEntityWithoutRelations.gridPosition,
configuration: configurationWithUniversalIdentifiers,
conditionalDisplay:
pageLayoutWidgetEntityWithoutRelations.conditionalDisplay,
universalConfiguration: configurationWithUniversalIdentifiers,
},
};
};
@@ -129,7 +129,6 @@ export const fromRowLevelPermissionPredicateEntityToFlatRowLevelPermissionPredic
roleUniversalIdentifier,
workspaceMemberFieldMetadataUniversalIdentifier,
rowLevelPermissionPredicateGroupUniversalIdentifier,
value: rowLevelPermissionPredicateEntityWithoutRelations.value,
},
};
};
@@ -81,7 +81,6 @@ export const fromViewFilterEntityToFlatViewFilter = ({
fieldMetadataUniversalIdentifier,
viewFilterGroupUniversalIdentifier,
viewUniversalIdentifier,
value: viewFilterEntityWithoutRelations.value,
},
};
};
@@ -37,12 +37,6 @@ export const fromLogicFunctionEntityToFlatLogicFunction = ({
__universal: {
universalIdentifier: logicFunctionEntity.universalIdentifier,
applicationUniversalIdentifier,
publishedVersions: logicFunctionEntity.publishedVersions,
toolInputSchema: logicFunctionEntity.toolInputSchema,
cronTriggerSettings: logicFunctionEntity.cronTriggerSettings,
databaseEventTriggerSettings:
logicFunctionEntity.databaseEventTriggerSettings,
httpRouteTriggerSettings: logicFunctionEntity.httpRouteTriggerSettings,
},
};
};
@@ -10,6 +10,7 @@ export enum ObjectMetadataExceptionCode {
INVALID_OBJECT_INPUT = 'INVALID_OBJECT_INPUT',
OBJECT_MUTATION_NOT_ALLOWED = 'OBJECT_MUTATION_NOT_ALLOWED',
OBJECT_ALREADY_EXISTS = 'OBJECT_ALREADY_EXISTS',
APPLICATION_NOT_FOUND = 'APPLICATION_NOT_FOUND',
MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD = 'MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD',
INVALID_ORM_OUTPUT = 'INVALID_ORM_OUTPUT',
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
@@ -28,6 +29,8 @@ const getObjectMetadataExceptionUserFriendlyMessage = (
return msg`This object cannot be modified.`;
case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS:
return msg`An object with this name already exists.`;
case ObjectMetadataExceptionCode.APPLICATION_NOT_FOUND:
return msg`Application not found.`;
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
return msg`Custom object is missing a label identifier field.`;
case ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT:
@@ -331,11 +331,24 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
featureFlagsMap: existingFeatureFlagsMap,
flatApplicationMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatObjectMetadataMaps',
'featureFlagsMap',
'flatApplicationMaps',
]);
const ownerFlatApplication = isDefined(applicationId)
? flatApplicationMaps.byId[applicationId]
: workspaceCustomFlatApplication;
if (!isDefined(ownerFlatApplication)) {
throw new ObjectMetadataException(
`Could not find related application ${applicationId}`,
ObjectMetadataExceptionCode.APPLICATION_NOT_FOUND,
);
}
const {
flatObjectMetadataToCreate,
flatIndexMetadataToCreate,
@@ -344,8 +357,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
} = fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({
createObjectInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
flatApplication: ownerFlatApplication,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
existingFeatureFlagsMap,
});
@@ -15,7 +15,13 @@ import {
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
workspaceId: string;
flatObjectMetadata: NonNullableRequired<
Pick<FlatObjectMetadata, 'id' | 'applicationId'>
Pick<
FlatObjectMetadata,
| 'id'
| 'applicationId'
| 'universalIdentifier'
| 'applicationUniversalIdentifier'
>
>;
skipNameField?: boolean;
};
@@ -26,7 +32,12 @@ export type DefaultFlatFieldForCustomObjectMaps = ReturnType<
// This could be replaced totally by an import schema + its transpilation when it's ready
export const buildDefaultFlatFieldMetadatasForCustomObject = ({
workspaceId,
flatObjectMetadata: { id: objectMetadataId, applicationId },
flatObjectMetadata: {
id: objectMetadataId,
applicationId,
applicationUniversalIdentifier,
universalIdentifier: objectMetadataUniversalIdentifier,
},
skipNameField = false,
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
const createdAt = new Date().toISOString();
@@ -65,6 +76,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const nameFieldId = skipNameField ? null : v4();
@@ -105,6 +126,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const createdAtFieldId = v4();
@@ -142,6 +173,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const updatedAtFieldId = v4();
@@ -179,6 +220,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const deletedAtFieldId = v4();
@@ -216,6 +267,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const createdByFieldId = v4();
@@ -252,6 +313,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const updatedByFieldId = v4();
@@ -288,6 +359,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const positionFieldId = v4();
@@ -325,6 +406,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
settings: null,
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
const searchVectorFieldId = v4();
@@ -367,6 +458,16 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
},
morphId: null,
applicationId,
applicationUniversalIdentifier,
objectMetadataUniversalIdentifier,
relationTargetObjectMetadataUniversalIdentifier: null,
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
universalSettings: null,
};
return {
@@ -5,6 +5,7 @@ import { capitalize, isDefined } from 'twenty-shared/utils';
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
@@ -53,7 +54,7 @@ export type BuildDefaultRelationFieldsForCustomObjectArgs = {
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
workspaceId: string;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceCustomApplicationId: string;
flatApplication: FlatApplication;
};
type SourceAndTargetFlatFieldMetadatasRecord = {
@@ -71,7 +72,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
}: BuildDefaultRelationFieldsForCustomObjectArgs): SourceAndTargetFlatFieldMetadatasRecord => {
const objectIdByNameSingular = Object.values(
existingFlatObjectMetadataMaps.byId,
@@ -156,7 +157,7 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({
? FieldMetadataType.MORPH_RELATION
: FieldMetadataType.RELATION,
workspaceId,
workspaceCustomApplicationId,
flatApplication,
sourceFlatObjectMetadataJoinColumnName: joinColumnName,
morphId,
targetFieldName: fieldName,
@@ -43,6 +43,7 @@ export const objectMetadataGraphqlApiExceptionHandler = (
case ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT:
throw new InternalServerError(error);
case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD:
case ObjectMetadataExceptionCode.APPLICATION_NOT_FOUND:
throw error;
default: {
return assertUnreachable(error.code);