Deprecate object metadata maps in favor of flat entities (#16080)
## Context Deprecating the old objectMetadataMap type in favour of split flat entities to match with our new caching. In the long run, trying to achieve: - Better performance through caching - Consistent data access patterns across the codebase - Reduced database queries Now that everything is based on flat entities, which are cached, we can finish the refactoring of workspace context cache which should already improve performances. Then the last step will be to consume that new cache in the new global datasource to get rid of the many workspace datasources stored in the server
This commit is contained in:
+9
-17
@@ -1,13 +1,13 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import {
|
||||
mockFlatFieldMetadataMaps,
|
||||
mockFlatObjectMetadataMaps,
|
||||
mockObjectIdByNameSingular,
|
||||
} from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||
import { type FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||
|
||||
const companyMockObjectMetadataItem = mockObjectMetadataItemsWithFieldMaps.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
describe('generateFakeFormResponse', () => {
|
||||
it('should generate fake responses for a form schema', async () => {
|
||||
const formFieldMetadataItems: FormFieldMetadata[] = [
|
||||
@@ -41,19 +41,11 @@ describe('generateFakeFormResponse', () => {
|
||||
},
|
||||
];
|
||||
|
||||
const mockObjectMetadataMaps = {
|
||||
byId: {
|
||||
[companyMockObjectMetadataItem.id]: companyMockObjectMetadataItem,
|
||||
},
|
||||
idByNameSingular: {
|
||||
[companyMockObjectMetadataItem.nameSingular]:
|
||||
companyMockObjectMetadataItem.id,
|
||||
},
|
||||
};
|
||||
|
||||
const result = generateFakeFormResponse({
|
||||
formFieldMetadataItems,
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
flatObjectMetadataMaps: mockFlatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps: mockFlatFieldMetadataMaps,
|
||||
objectIdByNameSingular: mockObjectIdByNameSingular,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -103,7 +95,7 @@ describe('generateFakeFormResponse', () => {
|
||||
},
|
||||
},
|
||||
name: {
|
||||
fieldMetadataId: 'nameFieldMetadataId',
|
||||
fieldMetadataId: 'nameFieldMetadataId-company',
|
||||
icon: 'test-field-icon',
|
||||
isLeaf: true,
|
||||
label: 'Name',
|
||||
|
||||
+7
-27
@@ -1,5 +1,5 @@
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { mockCompanyObjectMetadataInfo } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event';
|
||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||
|
||||
@@ -25,33 +25,13 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const companyMockObjectMetadataItem =
|
||||
mockObjectMetadataItemsWithFieldMaps.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
const mockObjectMetadataMaps = {
|
||||
byId: {
|
||||
[companyMockObjectMetadataItem.id]: companyMockObjectMetadataItem,
|
||||
},
|
||||
idByNameSingular: {
|
||||
[companyMockObjectMetadataItem.nameSingular]:
|
||||
companyMockObjectMetadataItem.id,
|
||||
},
|
||||
};
|
||||
|
||||
const objectMetadataInfo = {
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
objectMetadataItemWithFieldsMaps: companyMockObjectMetadataItem,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(generateObjectRecordFields as jest.Mock).mockReturnValue(mockFields);
|
||||
});
|
||||
|
||||
it('should generate record with "after" prefix for CREATED action', () => {
|
||||
const result = generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
DatabaseEventAction.CREATED,
|
||||
);
|
||||
|
||||
@@ -82,7 +62,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
|
||||
it('should generate record with "after" prefix for UPDATED action', () => {
|
||||
const result = generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
DatabaseEventAction.UPDATED,
|
||||
);
|
||||
|
||||
@@ -113,7 +93,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
|
||||
it('should generate record with "before" prefix for DELETED action', () => {
|
||||
const result = generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
DatabaseEventAction.DELETED,
|
||||
);
|
||||
|
||||
@@ -144,7 +124,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
|
||||
it('should generate record with "before" prefix for DESTROYED action', () => {
|
||||
const result = generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
DatabaseEventAction.DESTROYED,
|
||||
);
|
||||
|
||||
@@ -175,7 +155,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
|
||||
it('should generate record with "after" prefix for UPSERTED action', () => {
|
||||
const result = generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
DatabaseEventAction.UPSERTED,
|
||||
);
|
||||
|
||||
@@ -207,7 +187,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
||||
it('should throw error for unknown action', () => {
|
||||
expect(() => {
|
||||
generateFakeObjectRecordEvent(
|
||||
objectMetadataInfo,
|
||||
mockCompanyObjectMetadataInfo,
|
||||
'UNKNOWN' as DatabaseEventAction,
|
||||
);
|
||||
}).toThrow("Unknown action 'UNKNOWN'");
|
||||
|
||||
+8
-23
@@ -1,4 +1,4 @@
|
||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { mockCompanyObjectMetadataInfo } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||
|
||||
@@ -12,28 +12,11 @@ jest.mock(
|
||||
}),
|
||||
);
|
||||
|
||||
const companyMockObjectMetadataItem = mockObjectMetadataItemsWithFieldMaps.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
const mockObjectMetadataMaps = {
|
||||
byId: {
|
||||
[companyMockObjectMetadataItem.id]: companyMockObjectMetadataItem,
|
||||
},
|
||||
idByNameSingular: {
|
||||
[companyMockObjectMetadataItem.nameSingular]:
|
||||
companyMockObjectMetadataItem.id,
|
||||
},
|
||||
};
|
||||
|
||||
const objectMetadataInfo = {
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
objectMetadataItemWithFieldsMaps: companyMockObjectMetadataItem,
|
||||
};
|
||||
|
||||
describe('generateFakeObjectRecord', () => {
|
||||
it('should generate a record with correct object metadata', () => {
|
||||
const result = generateFakeObjectRecord({ objectMetadataInfo });
|
||||
const result = generateFakeObjectRecord({
|
||||
objectMetadataInfo: mockCompanyObjectMetadataInfo,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
object: {
|
||||
@@ -53,10 +36,12 @@ describe('generateFakeObjectRecord', () => {
|
||||
});
|
||||
|
||||
it('should call generateObjectRecordFields with the object metadata', () => {
|
||||
generateFakeObjectRecord({ objectMetadataInfo });
|
||||
generateFakeObjectRecord({
|
||||
objectMetadataInfo: mockCompanyObjectMetadataInfo,
|
||||
});
|
||||
|
||||
expect(generateObjectRecordFields).toHaveBeenCalledWith({
|
||||
objectMetadataInfo,
|
||||
objectMetadataInfo: mockCompanyObjectMetadataInfo,
|
||||
depth: 0,
|
||||
maxDepth: 1,
|
||||
});
|
||||
|
||||
+7
-23
@@ -1,6 +1,6 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { mockCompanyObjectMetadataInfo } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||
import { generateFakeRecordField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field';
|
||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
||||
@@ -17,26 +17,6 @@ describe('generateObjectRecordFields', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const companyMockObjectMetadataItem =
|
||||
mockObjectMetadataItemsWithFieldMaps.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
const mockObjectMetadataMaps = {
|
||||
byId: {
|
||||
[companyMockObjectMetadataItem.id]: companyMockObjectMetadataItem,
|
||||
},
|
||||
idByNameSingular: {
|
||||
[companyMockObjectMetadataItem.nameSingular]:
|
||||
companyMockObjectMetadataItem.id,
|
||||
},
|
||||
};
|
||||
|
||||
const objectMetadataInfo = {
|
||||
objectMetadataMaps: mockObjectMetadataMaps,
|
||||
objectMetadataItemWithFieldsMaps: companyMockObjectMetadataItem,
|
||||
};
|
||||
|
||||
it('should generate fields for valid fields only', () => {
|
||||
(shouldGenerateFieldFakeValue as jest.Mock).mockImplementation(
|
||||
(field) => field.type !== FieldMetadataType.RELATION,
|
||||
@@ -51,7 +31,9 @@ describe('generateObjectRecordFields', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const result = generateObjectRecordFields({ objectMetadataInfo });
|
||||
const result = generateObjectRecordFields({
|
||||
objectMetadataInfo: mockCompanyObjectMetadataInfo,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
domainName: {
|
||||
@@ -75,7 +57,9 @@ describe('generateObjectRecordFields', () => {
|
||||
it('should return empty object when no valid fields', () => {
|
||||
(shouldGenerateFieldFakeValue as jest.Mock).mockReturnValue(false);
|
||||
|
||||
const result = generateObjectRecordFields({ objectMetadataInfo });
|
||||
const result = generateObjectRecordFields({
|
||||
objectMetadataInfo: mockCompanyObjectMetadataInfo,
|
||||
});
|
||||
|
||||
expect(result).toEqual({});
|
||||
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(2);
|
||||
|
||||
+16
-43
@@ -1,99 +1,72 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
||||
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
|
||||
|
||||
describe('shouldGenerateFieldFakeValue', () => {
|
||||
it('should return true for active non-system fields', () => {
|
||||
const field = getMockFieldMetadataEntity({
|
||||
workspaceId: '20202020-0000-0000-0000-000000000000',
|
||||
const field = getFlatFieldMetadataMock({
|
||||
objectMetadataId: '20202020-0000-0000-0000-000000000001',
|
||||
id: '20202020-0000-0000-0000-000000000002',
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'test-field-universal-id',
|
||||
});
|
||||
|
||||
expect(shouldGenerateFieldFakeValue(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for system id field', () => {
|
||||
const field = getMockFieldMetadataEntity({
|
||||
workspaceId: '20202020-0000-0000-0000-000000000000',
|
||||
const field = getFlatFieldMetadataMock({
|
||||
objectMetadataId: '20202020-0000-0000-0000-000000000001',
|
||||
id: '20202020-0000-0000-0000-000000000003',
|
||||
isSystem: true,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.UUID,
|
||||
name: 'id',
|
||||
label: 'ID',
|
||||
isNullable: false,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'id-field-universal-id',
|
||||
});
|
||||
|
||||
expect(shouldGenerateFieldFakeValue(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for inactive fields', () => {
|
||||
const field = getMockFieldMetadataEntity({
|
||||
workspaceId: '20202020-0000-0000-0000-000000000000',
|
||||
const field = getFlatFieldMetadataMock({
|
||||
objectMetadataId: '20202020-0000-0000-0000-000000000001',
|
||||
id: '20202020-0000-0000-0000-000000000004',
|
||||
isSystem: false,
|
||||
isActive: false,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'inactive-field-universal-id',
|
||||
});
|
||||
|
||||
expect(shouldGenerateFieldFakeValue(field)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for system fields', () => {
|
||||
const field = getMockFieldMetadataEntity({
|
||||
workspaceId: '20202020-0000-0000-0000-000000000000',
|
||||
const field = getFlatFieldMetadataMock({
|
||||
objectMetadataId: '20202020-0000-0000-0000-000000000001',
|
||||
id: '20202020-0000-0000-0000-000000000005',
|
||||
isSystem: true,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'system-field-universal-id',
|
||||
});
|
||||
|
||||
expect(shouldGenerateFieldFakeValue(field)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for relation fields', () => {
|
||||
const field = getMockFieldMetadataEntity({
|
||||
workspaceId: '20202020-0000-0000-0000-000000000000',
|
||||
it('should return false for many-to-one relation fields', () => {
|
||||
const field = getFlatFieldMetadataMock({
|
||||
objectMetadataId: '20202020-0000-0000-0000-000000000001',
|
||||
id: '20202020-0000-0000-0000-000000000006',
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
universalIdentifier: 'relation-field-universal-id',
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
},
|
||||
});
|
||||
|
||||
expect(shouldGenerateFieldFakeValue(field)).toBe(false);
|
||||
|
||||
+23
-12
@@ -1,7 +1,8 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
type Leaf,
|
||||
type Node,
|
||||
@@ -12,12 +13,16 @@ import { type FormFieldMetadata } from 'src/modules/workflow/workflow-executor/w
|
||||
|
||||
type GenerateFakeFormResponseArgs = {
|
||||
formFieldMetadataItems: FormFieldMetadata[];
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
objectIdByNameSingular: Record<string, string>;
|
||||
};
|
||||
|
||||
export const generateFakeFormResponse = ({
|
||||
formFieldMetadataItems,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
}: GenerateFakeFormResponseArgs): Record<string, Leaf | Node> => {
|
||||
const result = formFieldMetadataItems.map((formFieldMetadata) => {
|
||||
if (formFieldMetadata.type === 'RECORD') {
|
||||
@@ -25,13 +30,18 @@ export const generateFakeFormResponse = ({
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const objectMetadataItemWithFieldsMaps =
|
||||
getObjectMetadataMapItemByNameSingular(
|
||||
objectMetadataMaps,
|
||||
formFieldMetadata?.settings?.objectName,
|
||||
);
|
||||
const objectId =
|
||||
objectIdByNameSingular[formFieldMetadata?.settings?.objectName];
|
||||
|
||||
if (!isDefined(objectMetadataItemWithFieldsMaps)) {
|
||||
if (!isDefined(objectId)) {
|
||||
throw new Error(
|
||||
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
|
||||
);
|
||||
}
|
||||
|
||||
const flatObjectMetadata = flatObjectMetadataMaps.byId[objectId];
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
throw new Error(
|
||||
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
|
||||
);
|
||||
@@ -43,8 +53,9 @@ export const generateFakeFormResponse = ({
|
||||
label: formFieldMetadata.label,
|
||||
value: generateFakeObjectRecord({
|
||||
objectMetadataInfo: {
|
||||
objectMetadataItemWithFieldsMaps,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
+5
-5
@@ -13,6 +13,7 @@ const generateFakeObjectRecordEventWithPrefix = ({
|
||||
objectMetadataInfo: ObjectMetadataInfo;
|
||||
prefix: string;
|
||||
}): RecordOutputSchema => {
|
||||
const { flatObjectMetadata } = objectMetadataInfo;
|
||||
const recordFields = generateObjectRecordFields({ objectMetadataInfo });
|
||||
const prefixedRecordFields = Object.entries(recordFields).reduce(
|
||||
(acc, [key, value]) => {
|
||||
@@ -26,12 +27,11 @@ const generateFakeObjectRecordEventWithPrefix = ({
|
||||
return {
|
||||
object: {
|
||||
isLeaf: true,
|
||||
icon:
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined,
|
||||
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
|
||||
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
|
||||
icon: flatObjectMetadata.icon ?? undefined,
|
||||
label: flatObjectMetadata.labelSingular,
|
||||
value: flatObjectMetadata.description,
|
||||
fieldIdName: `${prefix}.id`,
|
||||
objectMetadataId: objectMetadataInfo.objectMetadataItemWithFieldsMaps.id,
|
||||
objectMetadataId: flatObjectMetadata.id,
|
||||
},
|
||||
fields: prefixedRecordFields,
|
||||
_outputSchemaType: 'RECORD',
|
||||
|
||||
+6
-5
@@ -11,15 +11,16 @@ export const generateFakeObjectRecord = ({
|
||||
depth?: number;
|
||||
maxDepth?: number;
|
||||
}): RecordOutputSchema => {
|
||||
const { flatObjectMetadata } = objectMetadataInfo;
|
||||
|
||||
return {
|
||||
object: {
|
||||
isLeaf: true,
|
||||
icon:
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon ?? undefined,
|
||||
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
|
||||
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
|
||||
icon: flatObjectMetadata.icon ?? undefined,
|
||||
label: flatObjectMetadata.labelSingular,
|
||||
value: flatObjectMetadata.description,
|
||||
fieldIdName: 'id',
|
||||
objectMetadataId: objectMetadataInfo.objectMetadataItemWithFieldsMaps.id,
|
||||
objectMetadataId: flatObjectMetadata.id,
|
||||
},
|
||||
fields: generateObjectRecordFields({
|
||||
objectMetadataInfo,
|
||||
|
||||
+63
-58
@@ -1,6 +1,7 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { type ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||
import { type FieldOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
||||
@@ -17,67 +18,71 @@ export const generateObjectRecordFields = ({
|
||||
depth?: number;
|
||||
maxDepth?: number;
|
||||
}): Record<string, FieldOutputSchema> => {
|
||||
const objectMetadata = objectMetadataInfo.objectMetadataItemWithFieldsMaps;
|
||||
const { flatObjectMetadata, flatObjectMetadataMaps, flatFieldMetadataMaps } =
|
||||
objectMetadataInfo;
|
||||
|
||||
return Object.values(objectMetadata.fieldsById).reduce(
|
||||
(acc: Record<string, FieldOutputSchema>, field) => {
|
||||
if (!shouldGenerateFieldFakeValue(field)) {
|
||||
return acc;
|
||||
const result: Record<string, FieldOutputSchema> = {};
|
||||
|
||||
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const field = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: fieldId,
|
||||
});
|
||||
|
||||
if (!shouldGenerateFieldFakeValue(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type !== FieldMetadataType.RELATION) {
|
||||
result[field.name] = generateFakeRecordField({
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
icon: field.icon ?? undefined,
|
||||
fieldMetadataId: field.id,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isDefined(field.relationTargetObjectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (depth < maxDepth) {
|
||||
const relationTargetObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[field.relationTargetObjectMetadataId];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type !== FieldMetadataType.RELATION) {
|
||||
acc[field.name] = generateFakeRecordField({
|
||||
type: field.type,
|
||||
label: field.label,
|
||||
icon: field.icon ?? undefined,
|
||||
fieldMetadataId: field.id,
|
||||
});
|
||||
result[field.name] = {
|
||||
isLeaf: false,
|
||||
icon: field.icon ?? undefined,
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
fieldMetadataId: field.id,
|
||||
value: generateFakeObjectRecord({
|
||||
objectMetadataInfo: {
|
||||
flatObjectMetadata: relationTargetObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
},
|
||||
depth: depth + 1,
|
||||
}),
|
||||
};
|
||||
} else if (depth === maxDepth) {
|
||||
const relationIdFieldName = `${field.name}Id`;
|
||||
const relationIdFieldLabel = camelToTitleCase(relationIdFieldName);
|
||||
|
||||
return acc;
|
||||
}
|
||||
result[relationIdFieldName] = generateFakeRecordField({
|
||||
type: FieldMetadataType.UUID,
|
||||
label: relationIdFieldLabel,
|
||||
icon: field.icon ?? undefined,
|
||||
fieldMetadataId: field.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDefined(field.relationTargetObjectMetadataId)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (depth < maxDepth) {
|
||||
const relationTargetObjectMetadata =
|
||||
objectMetadataInfo.objectMetadataMaps.byId[
|
||||
field.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadata)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[field.name] = {
|
||||
isLeaf: false,
|
||||
icon: field.icon ?? undefined,
|
||||
label: field.label,
|
||||
type: field.type,
|
||||
fieldMetadataId: field.id,
|
||||
value: generateFakeObjectRecord({
|
||||
objectMetadataInfo: {
|
||||
objectMetadataItemWithFieldsMaps: relationTargetObjectMetadata,
|
||||
objectMetadataMaps: objectMetadataInfo.objectMetadataMaps,
|
||||
},
|
||||
depth: depth + 1,
|
||||
}),
|
||||
};
|
||||
} else if (depth === maxDepth) {
|
||||
const relationIdFieldName = `${field.name}Id`;
|
||||
const relationIdFieldLabel = camelToTitleCase(relationIdFieldName);
|
||||
|
||||
acc[relationIdFieldName] = generateFakeRecordField({
|
||||
type: FieldMetadataType.UUID,
|
||||
label: relationIdFieldLabel,
|
||||
icon: field.icon ?? undefined,
|
||||
fieldMetadataId: field.id,
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, FieldOutputSchema>,
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
+12
-11
@@ -1,21 +1,22 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { isFlatFieldMetadataOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
|
||||
const isManyToOneRelationField = (field: FieldMetadataEntity) =>
|
||||
(field as FieldMetadataEntity<FieldMetadataType.RELATION>).settings
|
||||
?.relationType === 'MANY_TO_ONE';
|
||||
const isManyToOneRelationField = (field: FlatFieldMetadata) => {
|
||||
if (isFlatFieldMetadataOfType(field, FieldMetadataType.RELATION)) {
|
||||
return field.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const EXCLUDED_SYSTEM_FIELDS = ['searchVector', 'position'];
|
||||
|
||||
// TODO refactor
|
||||
export const shouldGenerateFieldFakeValue = <T extends FieldMetadataType>(
|
||||
field: FieldMetadataEntity<T>,
|
||||
) => {
|
||||
export const shouldGenerateFieldFakeValue = (field: FlatFieldMetadata) => {
|
||||
return (
|
||||
field.isActive &&
|
||||
!(EXCLUDED_SYSTEM_FIELDS.includes(field.name) && field.isSystem) &&
|
||||
(field.type !== FieldMetadataType.RELATION ||
|
||||
isManyToOneRelationField(field as unknown as FieldMetadataEntity))
|
||||
!isManyToOneRelationField(field)
|
||||
);
|
||||
};
|
||||
|
||||
+22
-21
@@ -216,7 +216,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
}
|
||||
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
nameSingular,
|
||||
workspaceId,
|
||||
);
|
||||
@@ -241,14 +241,14 @@ export class WorkflowSchemaWorkspaceService {
|
||||
});
|
||||
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
objectType,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const first: Node = {
|
||||
isLeaf: false,
|
||||
label: `First ${objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular ?? 'Record'}`,
|
||||
label: `First ${objectMetadataInfo.flatObjectMetadata.labelSingular ?? 'Record'}`,
|
||||
icon: 'IconAlpha',
|
||||
type: 'object',
|
||||
value: recordOutputSchema,
|
||||
@@ -256,7 +256,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
|
||||
const all: Leaf = {
|
||||
isLeaf: true,
|
||||
label: `All ${objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelPlural ?? 'Records'}`,
|
||||
label: `All ${objectMetadataInfo.flatObjectMetadata.labelPlural ?? 'Records'}`,
|
||||
type: 'array',
|
||||
icon: 'IconListDetails',
|
||||
value: 'Returns an array of records',
|
||||
@@ -283,7 +283,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
maxDepth?: number;
|
||||
}): Promise<OutputSchema> {
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
objectType,
|
||||
workspaceId,
|
||||
);
|
||||
@@ -302,14 +302,18 @@ export class WorkflowSchemaWorkspaceService {
|
||||
formFieldMetadataItems: FormFieldMetadata[];
|
||||
workspaceId: string;
|
||||
}): Promise<OutputSchema> {
|
||||
const objectMetadataMaps =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataMaps(
|
||||
workspaceId,
|
||||
);
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
} =
|
||||
await this.workflowCommonWorkspaceService.getFlatEntityMaps(workspaceId);
|
||||
|
||||
return generateFakeFormResponse({
|
||||
formFieldMetadataItems,
|
||||
objectMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,20 +341,18 @@ export class WorkflowSchemaWorkspaceService {
|
||||
|
||||
if (availability.type === 'BULK_RECORDS') {
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
availability.objectNameSingular,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return {
|
||||
[objectMetadataInfo.objectMetadataItemWithFieldsMaps.namePlural]: {
|
||||
label:
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelPlural,
|
||||
[objectMetadataInfo.flatObjectMetadata.namePlural]: {
|
||||
label: objectMetadataInfo.flatObjectMetadata.labelPlural,
|
||||
isLeaf: true,
|
||||
type: 'array',
|
||||
value:
|
||||
'Array of ' +
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelPlural,
|
||||
'Array of ' + objectMetadataInfo.flatObjectMetadata.labelPlural,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -417,7 +419,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
case WorkflowTriggerType.MANUAL: {
|
||||
if (trigger.settings.availability?.type === 'BULK_RECORDS') {
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
trigger.settings.availability.objectNameSingular,
|
||||
workspaceId,
|
||||
);
|
||||
@@ -425,8 +427,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
return {
|
||||
label:
|
||||
'Current Item (' +
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps
|
||||
.labelSingular +
|
||||
objectMetadataInfo.flatObjectMetadata.labelSingular +
|
||||
')',
|
||||
isLeaf: false,
|
||||
type: 'object',
|
||||
@@ -468,7 +469,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
switch (step.type) {
|
||||
case WorkflowActionType.FIND_RECORDS: {
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
step.settings.input.objectName,
|
||||
workspaceId,
|
||||
);
|
||||
@@ -476,7 +477,7 @@ export class WorkflowSchemaWorkspaceService {
|
||||
return {
|
||||
label:
|
||||
'Current Item (' +
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular +
|
||||
objectMetadataInfo.flatObjectMetadata.labelSingular +
|
||||
')',
|
||||
isLeaf: false,
|
||||
type: 'object',
|
||||
|
||||
+6
-5
@@ -6,6 +6,7 @@ import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { BASE_TYPESCRIPT_PROJECT_INPUT_SCHEMA } from 'src/engine/core-modules/serverless/drivers/constants/base-typescript-project-input-schema';
|
||||
import { type WorkflowStepPositionInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position-input.dto';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
type WorkflowEmptyAction,
|
||||
type WorkflowFormAction,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
const BASE_STEP_DEFINITION: BaseWorkflowActionSettings = {
|
||||
outputSchema: {},
|
||||
errorHandlingOptions: {
|
||||
@@ -460,14 +460,15 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
isValidUuid(response[key].id)
|
||||
) {
|
||||
const objectMetadataInfo =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||
const { flatObjectMetadata, flatFieldMetadataMaps } =
|
||||
await this.workflowCommonWorkspaceService.getObjectMetadataInfo(
|
||||
field.settings.objectName,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relationFieldsNames = Object.values(
|
||||
objectMetadataInfo.objectMetadataItemWithFieldsMaps.fieldsById,
|
||||
const relationFieldsNames = getFlatFieldsFromFlatObjectMetadata(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.map((field) => field.name);
|
||||
|
||||
Reference in New Issue
Block a user