Add relations in database event trigger output data (#11820)
## Done - add relations in dropdown variables - add relations in worklfow run inputs - use objectMetadataMaps in workflow folder ## To do - does not work with rest api calls, will be fixed after https://github.com/twentyhq/twenty/pull/11349 is merged - waiting for crud action relation fields https://github.com/twentyhq/core-team-issues/issues/509
This commit is contained in:
@ -321,8 +321,8 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
this.apiEventEmitterService.emitUpdateEvents({
|
||||||
existingRecords: [existingRecord],
|
existingRecords: structuredClone([existingRecord]),
|
||||||
records: [record],
|
records: structuredClone([record]),
|
||||||
updatedFields: Object.keys(formattedPartialRecordToUpdate),
|
updatedFields: Object.keys(formattedPartialRecordToUpdate),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
@ -366,7 +366,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: formattedInsertedRecords,
|
records: structuredClone(formattedInsertedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: upsertedRecords,
|
records: structuredClone(upsertedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents({
|
this.apiEventEmitterService.emitDeletedEvents({
|
||||||
records: formattedDeletedRecords,
|
records: structuredClone(formattedDeletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -48,12 +48,6 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents({
|
|
||||||
records: formattedDeletedRecords,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (formattedDeletedRecords.length === 0) {
|
if (formattedDeletedRecords.length === 0) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
@ -63,6 +57,12 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
|
|
||||||
const deletedRecord = formattedDeletedRecords[0];
|
const deletedRecord = formattedDeletedRecords[0];
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitDeletedEvents({
|
||||||
|
records: structuredClone(formattedDeletedRecords),
|
||||||
|
authContext,
|
||||||
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents({
|
this.apiEventEmitterService.emitDestroyEvents({
|
||||||
records: deletedRecords,
|
records: structuredClone(deletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents({
|
this.apiEventEmitterService.emitDestroyEvents({
|
||||||
records: deletedRecords,
|
records: structuredClone(deletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitRestoreEvents({
|
this.apiEventEmitterService.emitRestoreEvents({
|
||||||
records: formattedRestoredRecords,
|
records: structuredClone(formattedRestoredRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -48,12 +48,6 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitRestoreEvents({
|
|
||||||
records: formattedRestoredRecords,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (formattedRestoredRecords.length === 0) {
|
if (formattedRestoredRecords.length === 0) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
@ -63,6 +57,12 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
|
|
||||||
const restoredRecord = formattedRestoredRecords[0];
|
const restoredRecord = formattedRestoredRecords[0];
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitRestoreEvents({
|
||||||
|
records: structuredClone(formattedRestoredRecords),
|
||||||
|
authContext,
|
||||||
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
|||||||
@ -90,8 +90,8 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
this.apiEventEmitterService.emitUpdateEvents({
|
||||||
existingRecords: formattedExistingRecords,
|
existingRecords: structuredClone(formattedExistingRecords),
|
||||||
records: formattedUpdatedRecords,
|
records: structuredClone(formattedUpdatedRecords),
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
updatedFields: Object.keys(executionArgs.args.data),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
@ -101,7 +101,10 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: formattedUpdatedRecords,
|
parentObjectRecords: [
|
||||||
|
...formattedExistingRecords,
|
||||||
|
...formattedUpdatedRecords,
|
||||||
|
],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
|
|||||||
@ -74,14 +74,6 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
|
||||||
existingRecords: formattedExistingRecords,
|
|
||||||
records: formattedUpdatedRecords,
|
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (formattedUpdatedRecords.length === 0) {
|
if (formattedUpdatedRecords.length === 0) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
@ -90,12 +82,21 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedRecord = formattedUpdatedRecords[0];
|
const updatedRecord = formattedUpdatedRecords[0];
|
||||||
|
const existingRecord = formattedExistingRecords[0];
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitUpdateEvents({
|
||||||
|
existingRecords: structuredClone(formattedExistingRecords),
|
||||||
|
records: structuredClone(formattedUpdatedRecords),
|
||||||
|
updatedFields: Object.keys(executionArgs.args.data),
|
||||||
|
authContext,
|
||||||
|
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: [updatedRecord],
|
parentObjectRecords: [existingRecord, updatedRecord],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelSingular: 'Person',
|
labelSingular: 'Person',
|
||||||
labelPlural: 'People',
|
labelPlural: 'People',
|
||||||
description: 'A person',
|
description: 'A person',
|
||||||
|
icon: 'test-person-icon',
|
||||||
targetTableName: 'DEPRECATED',
|
targetTableName: 'DEPRECATED',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
@ -24,13 +25,32 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [],
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'nameFieldMetadataId',
|
||||||
|
objectMetadataId: '',
|
||||||
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
icon: 'test-field-icon',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
defaultValue: {
|
||||||
|
lastName: "''",
|
||||||
|
firstName: "''",
|
||||||
|
},
|
||||||
|
description: 'Contact’s name',
|
||||||
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: false,
|
||||||
|
workspaceId: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.FULL_NAME,
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
@ -49,6 +69,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.FULL_NAME,
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
@ -72,6 +93,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelSingular: 'Company',
|
labelSingular: 'Company',
|
||||||
labelPlural: 'Companies',
|
labelPlural: 'Companies',
|
||||||
description: 'A company',
|
description: 'A company',
|
||||||
|
icon: 'test-company-icon',
|
||||||
targetTableName: 'DEPRECATED',
|
targetTableName: 'DEPRECATED',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
@ -84,13 +106,41 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [],
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'nameFieldMetadataId',
|
||||||
|
objectMetadataId: '',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
defaultValue: '',
|
||||||
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: false,
|
||||||
|
workspaceId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'domainNameFieldMetadataId',
|
||||||
|
objectMetadataId: '',
|
||||||
|
type: FieldMetadataType.LINKS,
|
||||||
|
icon: 'test-field-icon',
|
||||||
|
name: 'domainName',
|
||||||
|
label: 'Domain Name',
|
||||||
|
defaultValue: '',
|
||||||
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: false,
|
||||||
|
workspaceId: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -103,6 +153,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'domainNameFieldMetadataId',
|
id: 'domainNameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.LINKS,
|
type: FieldMetadataType.LINKS,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'domainName',
|
name: 'domainName',
|
||||||
label: 'Domain Name',
|
label: 'Domain Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -117,6 +168,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
@ -132,6 +184,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'domainNameFieldMetadataId',
|
id: 'domainNameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.LINKS,
|
type: FieldMetadataType.LINKS,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'domainName',
|
name: 'domainName',
|
||||||
label: 'Domain Name',
|
label: 'Domain Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -151,6 +204,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelSingular: 'Regular Custom Object',
|
labelSingular: 'Regular Custom Object',
|
||||||
labelPlural: 'Regular Custom Objects',
|
labelPlural: 'Regular Custom Objects',
|
||||||
description: 'A regular custom object',
|
description: 'A regular custom object',
|
||||||
|
icon: 'test-regular-custom-object-icon',
|
||||||
targetTableName: 'DEPRECATED',
|
targetTableName: 'DEPRECATED',
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
@ -163,13 +217,41 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId',
|
imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [],
|
fields: [
|
||||||
|
{
|
||||||
|
id: 'nameFieldMetadataId',
|
||||||
|
objectMetadataId: '',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
|
name: 'name',
|
||||||
|
label: 'Name',
|
||||||
|
defaultValue: '',
|
||||||
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: false,
|
||||||
|
workspaceId: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'imageIdentifierFieldMetadataId',
|
||||||
|
objectMetadataId: '',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
|
name: 'imageIdentifierFieldName',
|
||||||
|
label: 'Image Identifier Field Name',
|
||||||
|
defaultValue: '',
|
||||||
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
|
isUnique: false,
|
||||||
|
workspaceId: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -182,6 +264,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'imageIdentifierFieldMetadataId',
|
id: 'imageIdentifierFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'imageIdentifierFieldName',
|
name: 'imageIdentifierFieldName',
|
||||||
label: 'Image Identifier Field Name',
|
label: 'Image Identifier Field Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -196,6 +279,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'nameFieldMetadataId',
|
id: 'nameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
defaultValue: {
|
defaultValue: {
|
||||||
@ -211,6 +295,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
id: 'imageIdentifierFieldMetadataId',
|
id: 'imageIdentifierFieldMetadataId',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
|
icon: 'test-field-icon',
|
||||||
name: 'imageIdentifierFieldName',
|
name: 'imageIdentifierFieldName',
|
||||||
label: 'Image Identifier Field Name',
|
label: 'Image Identifier Field Name',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
@ -230,6 +315,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelSingular: '',
|
labelSingular: '',
|
||||||
labelPlural: '',
|
labelPlural: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
icon: 'test-non-searchable-object-icon',
|
||||||
targetTableName: 'DEPRECATED',
|
targetTableName: 'DEPRECATED',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||||
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
|
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
|
||||||
import { encodeCursorData } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
import { encodeCursorData } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|||||||
@ -3,10 +3,8 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
|||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { RelationMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-metadata.interface';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
|
|
||||||
export interface FieldMetadataInterface<
|
export interface FieldMetadataInterface<
|
||||||
T extends FieldMetadataType = FieldMetadataType,
|
T extends FieldMetadataType = FieldMetadataType,
|
||||||
@ -21,14 +19,15 @@ export interface FieldMetadataInterface<
|
|||||||
objectMetadataId: string;
|
objectMetadataId: string;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
icon?: string;
|
||||||
isNullable?: boolean;
|
isNullable?: boolean;
|
||||||
isUnique?: boolean;
|
isUnique?: boolean;
|
||||||
fromRelationMetadata?: RelationMetadataEntity;
|
fromRelationMetadata?: RelationMetadataInterface;
|
||||||
toRelationMetadata?: RelationMetadataEntity;
|
toRelationMetadata?: RelationMetadataInterface;
|
||||||
relationTargetFieldMetadataId?: string;
|
relationTargetFieldMetadataId?: string;
|
||||||
relationTargetFieldMetadata?: FieldMetadataEntity;
|
relationTargetFieldMetadata?: FieldMetadataInterface;
|
||||||
relationTargetObjectMetadataId?: string;
|
relationTargetObjectMetadataId?: string;
|
||||||
relationTargetObjectMetadata?: ObjectMetadataEntity;
|
relationTargetObjectMetadata?: ObjectMetadataInterface;
|
||||||
isCustom?: boolean;
|
isCustom?: boolean;
|
||||||
isSystem?: boolean;
|
isSystem?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export interface ObjectMetadataInterface {
|
|||||||
labelSingular: string;
|
labelSingular: string;
|
||||||
labelPlural: string;
|
labelPlural: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
icon?: string;
|
||||||
targetTableName: string;
|
targetTableName: string;
|
||||||
fromRelations: RelationMetadataInterface[];
|
fromRelations: RelationMetadataInterface[];
|
||||||
toRelations: RelationMetadataInterface[];
|
toRelations: RelationMetadataInterface[];
|
||||||
|
|||||||
@ -7,4 +7,8 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
|||||||
export const removeFieldMapsFromObjectMetadata = (
|
export const removeFieldMapsFromObjectMetadata = (
|
||||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
): ObjectMetadataInterface =>
|
): ObjectMetadataInterface =>
|
||||||
omit(objectMetadata, ['fieldsById', 'fieldsByName']);
|
omit(objectMetadata, [
|
||||||
|
'fieldsById',
|
||||||
|
'fieldsByName',
|
||||||
|
'fieldsByJoinColumnName',
|
||||||
|
]);
|
||||||
|
|||||||
@ -20,6 +20,11 @@ import {
|
|||||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
import { WorkflowAutomatedTriggerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
|
import { WorkflowAutomatedTriggerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
|
||||||
|
|
||||||
|
export type ObjectMetadataInfo = {
|
||||||
|
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps;
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowCommonWorkspaceService {
|
export class WorkflowCommonWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -73,13 +78,9 @@ export class WorkflowCommonWorkspaceService {
|
|||||||
return { ...workflowVersion, trigger: workflowVersion.trigger };
|
return { ...workflowVersion, trigger: workflowVersion.trigger };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getObjectMetadataItemWithFieldsMaps(
|
async getObjectMetadataMaps(
|
||||||
objectNameSingular: string,
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<{
|
): Promise<ObjectMetadataMaps> {
|
||||||
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps;
|
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
|
||||||
}> {
|
|
||||||
const currentCacheVersion =
|
const currentCacheVersion =
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||||
|
|
||||||
@ -103,6 +104,15 @@ export class WorkflowCommonWorkspaceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return objectMetadataMaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectMetadataItemWithFieldsMaps(
|
||||||
|
objectNameSingular: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<ObjectMetadataInfo> {
|
||||||
|
const objectMetadataMaps = await this.getObjectMetadataMaps(workspaceId);
|
||||||
|
|
||||||
const objectMetadataItemWithFieldsMaps =
|
const objectMetadataItemWithFieldsMaps =
|
||||||
getObjectMetadataMapItemByNameSingular(
|
getObjectMetadataMapItemByNameSingular(
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||||
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||||
|
|
||||||
@ -9,15 +9,6 @@ const companyMockObjectMetadataItem = mockObjectMetadataItemsWithFieldMaps.find(
|
|||||||
)!;
|
)!;
|
||||||
|
|
||||||
describe('generateFakeFormResponse', () => {
|
describe('generateFakeFormResponse', () => {
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
let objectMetadataRepository;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
objectMetadataRepository = {
|
|
||||||
findOneOrFail: jest.fn().mockResolvedValue(companyMockObjectMetadataItem),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should generate fake responses for a form schema', async () => {
|
it('should generate fake responses for a form schema', async () => {
|
||||||
const schema: FormFieldMetadata[] = [
|
const schema: FormFieldMetadata[] = [
|
||||||
{
|
{
|
||||||
@ -50,11 +41,19 @@ describe('generateFakeFormResponse', () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const mockObjectMetadataMaps = {
|
||||||
|
byId: {
|
||||||
|
[companyMockObjectMetadataItem.id]: companyMockObjectMetadataItem,
|
||||||
|
},
|
||||||
|
idByNameSingular: {
|
||||||
|
[companyMockObjectMetadataItem.nameSingular]:
|
||||||
|
companyMockObjectMetadataItem.id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const result = await generateFakeFormResponse({
|
const result = await generateFakeFormResponse({
|
||||||
formMetadata: schema,
|
formMetadata: schema,
|
||||||
workspaceId: '1',
|
objectMetadataMaps: mockObjectMetadataMaps,
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -82,7 +81,7 @@ describe('generateFakeFormResponse', () => {
|
|||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
label: 'Company',
|
label: 'Company',
|
||||||
fieldIdName: 'id',
|
fieldIdName: 'id',
|
||||||
icon: undefined,
|
icon: 'test-company-icon',
|
||||||
nameSingular: 'company',
|
nameSingular: 'company',
|
||||||
value: 'A company',
|
value: 'A company',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event';
|
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';
|
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||||
|
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields',
|
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields',
|
||||||
@ -12,35 +12,48 @@ describe('generateFakeObjectRecordEvent', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockObjectMetadata = {
|
|
||||||
icon: 'test-icon',
|
|
||||||
labelSingular: 'Test Object',
|
|
||||||
description: 'Test Description',
|
|
||||||
nameSingular: 'testObject',
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
|
|
||||||
const mockFields = {
|
const mockFields = {
|
||||||
field1: { type: 'TEXT', value: 'test' },
|
field1: { type: 'TEXT', value: 'test' },
|
||||||
field2: { type: 'NUMBER', value: 123 },
|
field2: { type: 'NUMBER', value: 123 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
beforeEach(() => {
|
||||||
(generateObjectRecordFields as jest.Mock).mockReturnValue(mockFields);
|
(generateObjectRecordFields as jest.Mock).mockReturnValue(mockFields);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should generate record with "after" prefix for CREATED action', () => {
|
it('should generate record with "after" prefix for CREATED action', () => {
|
||||||
const result = generateFakeObjectRecordEvent(
|
const result = generateFakeObjectRecordEvent(
|
||||||
mockObjectMetadata,
|
objectMetadataInfo,
|
||||||
DatabaseEventAction.CREATED,
|
DatabaseEventAction.CREATED,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: 'test-icon',
|
icon: 'test-company-icon',
|
||||||
label: 'Test Object',
|
label: 'Company',
|
||||||
value: 'Test Description',
|
value: 'A company',
|
||||||
nameSingular: 'testObject',
|
nameSingular: 'company',
|
||||||
fieldIdName: 'properties.after.id',
|
fieldIdName: 'properties.after.id',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
@ -53,17 +66,17 @@ describe('generateFakeObjectRecordEvent', () => {
|
|||||||
|
|
||||||
it('should generate record with "after" prefix for UPDATED action', () => {
|
it('should generate record with "after" prefix for UPDATED action', () => {
|
||||||
const result = generateFakeObjectRecordEvent(
|
const result = generateFakeObjectRecordEvent(
|
||||||
mockObjectMetadata,
|
objectMetadataInfo,
|
||||||
DatabaseEventAction.UPDATED,
|
DatabaseEventAction.UPDATED,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: 'test-icon',
|
icon: 'test-company-icon',
|
||||||
label: 'Test Object',
|
label: 'Company',
|
||||||
value: 'Test Description',
|
value: 'A company',
|
||||||
nameSingular: 'testObject',
|
nameSingular: 'company',
|
||||||
fieldIdName: 'properties.after.id',
|
fieldIdName: 'properties.after.id',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
@ -76,17 +89,17 @@ describe('generateFakeObjectRecordEvent', () => {
|
|||||||
|
|
||||||
it('should generate record with "before" prefix for DELETED action', () => {
|
it('should generate record with "before" prefix for DELETED action', () => {
|
||||||
const result = generateFakeObjectRecordEvent(
|
const result = generateFakeObjectRecordEvent(
|
||||||
mockObjectMetadata,
|
objectMetadataInfo,
|
||||||
DatabaseEventAction.DELETED,
|
DatabaseEventAction.DELETED,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: 'test-icon',
|
icon: 'test-company-icon',
|
||||||
label: 'Test Object',
|
label: 'Company',
|
||||||
value: 'Test Description',
|
value: 'A company',
|
||||||
nameSingular: 'testObject',
|
nameSingular: 'company',
|
||||||
fieldIdName: 'properties.before.id',
|
fieldIdName: 'properties.before.id',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
@ -99,17 +112,17 @@ describe('generateFakeObjectRecordEvent', () => {
|
|||||||
|
|
||||||
it('should generate record with "before" prefix for DESTROYED action', () => {
|
it('should generate record with "before" prefix for DESTROYED action', () => {
|
||||||
const result = generateFakeObjectRecordEvent(
|
const result = generateFakeObjectRecordEvent(
|
||||||
mockObjectMetadata,
|
objectMetadataInfo,
|
||||||
DatabaseEventAction.DESTROYED,
|
DatabaseEventAction.DESTROYED,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: 'test-icon',
|
icon: 'test-company-icon',
|
||||||
label: 'Test Object',
|
label: 'Company',
|
||||||
value: 'Test Description',
|
value: 'A company',
|
||||||
nameSingular: 'testObject',
|
nameSingular: 'company',
|
||||||
fieldIdName: 'properties.before.id',
|
fieldIdName: 'properties.before.id',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
@ -123,7 +136,7 @@ describe('generateFakeObjectRecordEvent', () => {
|
|||||||
it('should throw error for unknown action', () => {
|
it('should throw error for unknown action', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
generateFakeObjectRecordEvent(
|
generateFakeObjectRecordEvent(
|
||||||
mockObjectMetadata,
|
objectMetadataInfo,
|
||||||
'UNKNOWN' as DatabaseEventAction,
|
'UNKNOWN' as DatabaseEventAction,
|
||||||
);
|
);
|
||||||
}).toThrow("Unknown action 'UNKNOWN'");
|
}).toThrow("Unknown action 'UNKNOWN'");
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
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';
|
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||||
|
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields',
|
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields',
|
||||||
@ -12,24 +12,36 @@ 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', () => {
|
describe('generateFakeObjectRecord', () => {
|
||||||
it('should generate a record with correct object metadata', () => {
|
it('should generate a record with correct object metadata', () => {
|
||||||
const mockObjectMetadata = {
|
const result = generateFakeObjectRecord(objectMetadataInfo);
|
||||||
icon: 'test-icon',
|
|
||||||
labelSingular: 'Test Object',
|
|
||||||
description: 'Test Description',
|
|
||||||
nameSingular: 'testObject',
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
|
|
||||||
const result = generateFakeObjectRecord(mockObjectMetadata);
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: 'test-icon',
|
icon: 'test-company-icon',
|
||||||
label: 'Test Object',
|
label: 'Company',
|
||||||
value: 'Test Description',
|
value: 'A company',
|
||||||
nameSingular: 'testObject',
|
nameSingular: 'company',
|
||||||
fieldIdName: 'id',
|
fieldIdName: 'id',
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
@ -41,15 +53,10 @@ describe('generateFakeObjectRecord', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call generateObjectRecordFields with the object metadata', () => {
|
it('should call generateObjectRecordFields with the object metadata', () => {
|
||||||
const mockObjectMetadata = {
|
generateFakeObjectRecord(objectMetadataInfo);
|
||||||
icon: 'test-icon',
|
|
||||||
labelSingular: 'Test Object',
|
|
||||||
description: 'Test Description',
|
|
||||||
nameSingular: 'testObject',
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
|
|
||||||
generateFakeObjectRecord(mockObjectMetadata);
|
expect(generateObjectRecordFields).toHaveBeenCalledWith({
|
||||||
|
objectMetadataInfo,
|
||||||
expect(generateObjectRecordFields).toHaveBeenCalledWith(mockObjectMetadata);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
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';
|
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
||||||
|
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field',
|
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field',
|
||||||
@ -17,38 +17,27 @@ describe('generateObjectRecordFields', () => {
|
|||||||
jest.clearAllMocks();
|
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', () => {
|
it('should generate fields for valid fields only', () => {
|
||||||
const mockFields = [
|
|
||||||
{
|
|
||||||
name: 'field1',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
label: 'Field 1',
|
|
||||||
icon: 'icon1',
|
|
||||||
isSystem: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'field2',
|
|
||||||
type: FieldMetadataType.RELATION,
|
|
||||||
label: 'Field 2',
|
|
||||||
icon: 'icon2',
|
|
||||||
isSystem: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'field3',
|
|
||||||
type: FieldMetadataType.NUMBER,
|
|
||||||
label: 'Field 3',
|
|
||||||
icon: 'icon3',
|
|
||||||
isSystem: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockObjectMetadata = {
|
|
||||||
fields: mockFields,
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
|
|
||||||
(shouldGenerateFieldFakeValue as jest.Mock).mockImplementation(
|
(shouldGenerateFieldFakeValue as jest.Mock).mockImplementation(
|
||||||
(field) => field.type !== FieldMetadataType.RELATION,
|
(field) => field.type !== FieldMetadataType.RELATION,
|
||||||
);
|
);
|
||||||
@ -62,49 +51,34 @@ describe('generateObjectRecordFields', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = generateObjectRecordFields(mockObjectMetadata);
|
const result = generateObjectRecordFields({ objectMetadataInfo });
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
field1: {
|
domainName: {
|
||||||
type: FieldMetadataType.TEXT,
|
icon: 'test-field-icon',
|
||||||
label: 'Field 1',
|
label: 'Domain Name',
|
||||||
icon: 'icon1',
|
type: 'LINKS',
|
||||||
value: 'mock-TEXT',
|
value: 'mock-LINKS',
|
||||||
},
|
},
|
||||||
field3: {
|
name: {
|
||||||
type: FieldMetadataType.NUMBER,
|
icon: 'test-field-icon',
|
||||||
label: 'Field 3',
|
label: 'Name',
|
||||||
icon: 'icon3',
|
type: 'TEXT',
|
||||||
value: 'mock-NUMBER',
|
value: 'mock-TEXT',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(3);
|
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(2);
|
||||||
expect(generateFakeField).toHaveBeenCalledTimes(2);
|
expect(generateFakeField).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty object when no valid fields', () => {
|
it('should return empty object when no valid fields', () => {
|
||||||
const mockFields = [
|
|
||||||
{
|
|
||||||
name: 'field1',
|
|
||||||
type: FieldMetadataType.RELATION,
|
|
||||||
label: 'Field 1',
|
|
||||||
icon: 'icon1',
|
|
||||||
isSystem: false,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const mockObjectMetadata = {
|
|
||||||
fields: mockFields,
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
|
|
||||||
(shouldGenerateFieldFakeValue as jest.Mock).mockReturnValue(false);
|
(shouldGenerateFieldFakeValue as jest.Mock).mockReturnValue(false);
|
||||||
|
|
||||||
const result = generateObjectRecordFields(mockObjectMetadata);
|
const result = generateObjectRecordFields({ objectMetadataInfo });
|
||||||
|
|
||||||
expect(result).toEqual({});
|
expect(result).toEqual({});
|
||||||
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(1);
|
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(2);
|
||||||
expect(generateFakeField).not.toHaveBeenCalled();
|
expect(generateFakeField).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,15 +19,7 @@ export const generateFakeField = ({
|
|||||||
}): Leaf | Node => {
|
}): Leaf | Node => {
|
||||||
const compositeType = compositeTypeDefinitions.get(type);
|
const compositeType = compositeTypeDefinitions.get(type);
|
||||||
|
|
||||||
if (!compositeType) {
|
if (compositeType) {
|
||||||
return {
|
|
||||||
isLeaf: true,
|
|
||||||
type: type,
|
|
||||||
icon: icon,
|
|
||||||
label: label,
|
|
||||||
value: generateFakeValue(type, 'FieldMetadataType'),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
return {
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
@ -45,4 +37,12 @@ export const generateFakeField = ({
|
|||||||
}, {}),
|
}, {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isLeaf: true,
|
||||||
|
type: type,
|
||||||
|
icon: icon,
|
||||||
|
label: label,
|
||||||
|
value: generateFakeValue(type, 'FieldMetadataType'),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import {
|
import {
|
||||||
Leaf,
|
Leaf,
|
||||||
Node,
|
Node,
|
||||||
@ -9,15 +7,15 @@ import {
|
|||||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||||
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
|
||||||
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
import { FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
|
||||||
|
import { 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';
|
||||||
|
|
||||||
export const generateFakeFormResponse = async ({
|
export const generateFakeFormResponse = async ({
|
||||||
formMetadata,
|
formMetadata,
|
||||||
workspaceId,
|
objectMetadataMaps,
|
||||||
objectMetadataRepository,
|
|
||||||
}: {
|
}: {
|
||||||
formMetadata: FormFieldMetadata[];
|
formMetadata: FormFieldMetadata[];
|
||||||
workspaceId: string;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
}): Promise<Record<string, Leaf | Node>> => {
|
}): Promise<Record<string, Leaf | Node>> => {
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
formMetadata.map(async (formFieldMetadata) => {
|
formMetadata.map(async (formFieldMetadata) => {
|
||||||
@ -26,19 +24,25 @@ export const generateFakeFormResponse = async ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata = await objectMetadataRepository.findOneOrFail({
|
const objectMetadataItemWithFieldsMaps =
|
||||||
where: {
|
getObjectMetadataMapItemByNameSingular(
|
||||||
nameSingular: formFieldMetadata?.settings?.objectName,
|
objectMetadataMaps,
|
||||||
workspaceId,
|
formFieldMetadata?.settings?.objectName,
|
||||||
},
|
);
|
||||||
relations: ['fields'],
|
|
||||||
});
|
if (!objectMetadataItemWithFieldsMaps)
|
||||||
|
throw new Error(
|
||||||
|
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[formFieldMetadata.name]: {
|
[formFieldMetadata.name]: {
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
label: formFieldMetadata.label,
|
label: formFieldMetadata.label,
|
||||||
value: generateFakeObjectRecord(objectMetadata),
|
value: generateFakeObjectRecord({
|
||||||
|
objectMetadataMaps,
|
||||||
|
objectMetadataItemWithFieldsMaps,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import {
|
import {
|
||||||
BaseOutputSchema,
|
BaseOutputSchema,
|
||||||
RecordOutputSchema,
|
RecordOutputSchema,
|
||||||
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||||
|
import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||||
|
|
||||||
const generateFakeObjectRecordEventWithPrefix = ({
|
const generateFakeObjectRecordEventWithPrefix = ({
|
||||||
objectMetadataEntity,
|
objectMetadataInfo,
|
||||||
prefix,
|
prefix,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataEntity: ObjectMetadataEntity;
|
objectMetadataInfo: ObjectMetadataInfo;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
}): RecordOutputSchema => {
|
}): RecordOutputSchema => {
|
||||||
const recordFields = generateObjectRecordFields(objectMetadataEntity);
|
const recordFields = generateObjectRecordFields({ objectMetadataInfo });
|
||||||
const prefixedRecordFields = Object.entries(recordFields).reduce(
|
const prefixedRecordFields = Object.entries(recordFields).reduce(
|
||||||
(acc, [key, value]) => {
|
(acc, [key, value]) => {
|
||||||
acc[`${prefix}.${key}`] = value;
|
acc[`${prefix}.${key}`] = value;
|
||||||
@ -26,10 +26,11 @@ const generateFakeObjectRecordEventWithPrefix = ({
|
|||||||
return {
|
return {
|
||||||
object: {
|
object: {
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
icon: objectMetadataEntity.icon,
|
icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon,
|
||||||
label: objectMetadataEntity.labelSingular,
|
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
|
||||||
value: objectMetadataEntity.description,
|
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
|
||||||
nameSingular: objectMetadataEntity.nameSingular,
|
nameSingular:
|
||||||
|
objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular,
|
||||||
fieldIdName: `${prefix}.id`,
|
fieldIdName: `${prefix}.id`,
|
||||||
},
|
},
|
||||||
fields: prefixedRecordFields,
|
fields: prefixedRecordFields,
|
||||||
@ -38,20 +39,20 @@ const generateFakeObjectRecordEventWithPrefix = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const generateFakeObjectRecordEvent = (
|
export const generateFakeObjectRecordEvent = (
|
||||||
objectMetadataEntity: ObjectMetadataEntity,
|
objectMetadataInfo: ObjectMetadataInfo,
|
||||||
action: DatabaseEventAction,
|
action: DatabaseEventAction,
|
||||||
): RecordOutputSchema => {
|
): RecordOutputSchema => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case DatabaseEventAction.CREATED:
|
case DatabaseEventAction.CREATED:
|
||||||
case DatabaseEventAction.UPDATED:
|
case DatabaseEventAction.UPDATED:
|
||||||
return generateFakeObjectRecordEventWithPrefix({
|
return generateFakeObjectRecordEventWithPrefix({
|
||||||
objectMetadataEntity,
|
objectMetadataInfo,
|
||||||
prefix: 'properties.after',
|
prefix: 'properties.after',
|
||||||
});
|
});
|
||||||
case DatabaseEventAction.DELETED:
|
case DatabaseEventAction.DELETED:
|
||||||
case DatabaseEventAction.DESTROYED:
|
case DatabaseEventAction.DESTROYED:
|
||||||
return generateFakeObjectRecordEventWithPrefix({
|
return generateFakeObjectRecordEventWithPrefix({
|
||||||
objectMetadataEntity,
|
objectMetadataInfo,
|
||||||
prefix: 'properties.before',
|
prefix: 'properties.before',
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { RecordOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
import { RecordOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||||
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
|
||||||
|
import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
|
|
||||||
export const generateFakeObjectRecord = (
|
export const generateFakeObjectRecord = (
|
||||||
objectMetadataEntity: ObjectMetadataEntity,
|
objectMetadataInfo: ObjectMetadataInfo,
|
||||||
): RecordOutputSchema => ({
|
): RecordOutputSchema => {
|
||||||
object: {
|
return {
|
||||||
isLeaf: true,
|
object: {
|
||||||
icon: objectMetadataEntity.icon,
|
isLeaf: true,
|
||||||
label: objectMetadataEntity.labelSingular,
|
icon: objectMetadataInfo.objectMetadataItemWithFieldsMaps.icon,
|
||||||
value: objectMetadataEntity.description,
|
label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelSingular,
|
||||||
nameSingular: objectMetadataEntity.nameSingular,
|
value: objectMetadataInfo.objectMetadataItemWithFieldsMaps.description,
|
||||||
fieldIdName: 'id',
|
nameSingular:
|
||||||
},
|
objectMetadataInfo.objectMetadataItemWithFieldsMaps.nameSingular,
|
||||||
fields: generateObjectRecordFields(objectMetadataEntity),
|
fieldIdName: 'id',
|
||||||
_outputSchemaType: 'RECORD',
|
},
|
||||||
});
|
fields: generateObjectRecordFields({ objectMetadataInfo }),
|
||||||
|
_outputSchemaType: 'RECORD',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -1,21 +1,60 @@
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { BaseOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
import { BaseOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||||
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
|
||||||
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
|
||||||
|
import { ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
|
|
||||||
export const generateObjectRecordFields = (
|
const MAXIMUM_DEPTH = 1;
|
||||||
objectMetadataEntity: ObjectMetadataEntity,
|
|
||||||
): BaseOutputSchema =>
|
export const generateObjectRecordFields = ({
|
||||||
objectMetadataEntity.fields.reduce((acc: BaseOutputSchema, field) => {
|
objectMetadataInfo,
|
||||||
|
depth = 0,
|
||||||
|
}: {
|
||||||
|
objectMetadataInfo: ObjectMetadataInfo;
|
||||||
|
depth?: number;
|
||||||
|
}): BaseOutputSchema => {
|
||||||
|
const objectMetadata = objectMetadataInfo.objectMetadataItemWithFieldsMaps;
|
||||||
|
|
||||||
|
return objectMetadata.fields.reduce((acc: BaseOutputSchema, field) => {
|
||||||
if (!shouldGenerateFieldFakeValue(field)) {
|
if (!shouldGenerateFieldFakeValue(field)) {
|
||||||
return acc;
|
return acc;
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[field.name] = generateFakeField({
|
if (field.type !== FieldMetadataType.RELATION) {
|
||||||
type: field.type,
|
acc[field.name] = generateFakeField({
|
||||||
label: field.label,
|
type: field.type,
|
||||||
icon: field.icon,
|
label: field.label,
|
||||||
});
|
icon: field.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
depth < MAXIMUM_DEPTH &&
|
||||||
|
isDefined(field.relationTargetObjectMetadataId)
|
||||||
|
) {
|
||||||
|
const relationTargetObjectMetadata =
|
||||||
|
objectMetadataInfo.objectMetadataMaps.byId[
|
||||||
|
field.relationTargetObjectMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
acc[field.name] = {
|
||||||
|
isLeaf: false,
|
||||||
|
icon: field.icon,
|
||||||
|
label: field.label,
|
||||||
|
value: generateObjectRecordFields({
|
||||||
|
objectMetadataInfo: {
|
||||||
|
objectMetadataItemWithFieldsMaps: relationTargetObjectMetadata,
|
||||||
|
objectMetadataMaps: objectMetadataInfo.objectMetadataMaps,
|
||||||
|
},
|
||||||
|
depth: depth + 1,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as BaseOutputSchema);
|
}, {} as BaseOutputSchema);
|
||||||
|
};
|
||||||
|
|||||||
@ -1,11 +1,18 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
export const shouldGenerateFieldFakeValue = (field: FieldMetadataEntity) => {
|
const isManyToOneRelationField = (field: FieldMetadataInterface) =>
|
||||||
|
(field as FieldMetadataEntity<FieldMetadataType.RELATION>).settings
|
||||||
|
?.relationType === 'MANY_TO_ONE';
|
||||||
|
|
||||||
|
export const shouldGenerateFieldFakeValue = (field: FieldMetadataInterface) => {
|
||||||
return (
|
return (
|
||||||
(!field.isSystem || field.name === 'id') &&
|
|
||||||
field.isActive &&
|
field.isActive &&
|
||||||
field.type !== FieldMetadataType.RELATION
|
(!field.isSystem || field.name === 'id' || field.name === 'userEmail') &&
|
||||||
|
(field.type !== FieldMetadataType.RELATION ||
|
||||||
|
isManyToOneRelationField(field))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||||
|
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata')],
|
imports: [WorkflowCommonModule],
|
||||||
providers: [WorkflowSchemaWorkspaceService],
|
providers: [WorkflowSchemaWorkspaceService],
|
||||||
exports: [WorkflowSchemaWorkspaceService],
|
exports: [WorkflowSchemaWorkspaceService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
|
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
|
||||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||||
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
import { generateFakeFormResponse } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-response';
|
||||||
@ -21,12 +16,12 @@ import {
|
|||||||
WorkflowTrigger,
|
WorkflowTrigger,
|
||||||
WorkflowTriggerType,
|
WorkflowTriggerType,
|
||||||
} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
} from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||||
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowSchemaWorkspaceService {
|
export class WorkflowSchemaWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async computeStepOutputSchema({
|
async computeStepOutputSchema({
|
||||||
@ -43,7 +38,6 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
return this.computeDatabaseEventTriggerOutputSchema({
|
return this.computeDatabaseEventTriggerOutputSchema({
|
||||||
eventName: step.settings.eventName,
|
eventName: step.settings.eventName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository: this.objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case WorkflowTriggerType.MANUAL: {
|
case WorkflowTriggerType.MANUAL: {
|
||||||
@ -56,7 +50,6 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
return this.computeRecordOutputSchema({
|
return this.computeRecordOutputSchema({
|
||||||
objectType,
|
objectType,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository: this.objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case WorkflowTriggerType.WEBHOOK:
|
case WorkflowTriggerType.WEBHOOK:
|
||||||
@ -72,19 +65,16 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
return this.computeRecordOutputSchema({
|
return this.computeRecordOutputSchema({
|
||||||
objectType: step.settings.input.objectName,
|
objectType: step.settings.input.objectName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository: this.objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
case WorkflowActionType.FIND_RECORDS:
|
case WorkflowActionType.FIND_RECORDS:
|
||||||
return this.computeFindRecordsOutputSchema({
|
return this.computeFindRecordsOutputSchema({
|
||||||
objectType: step.settings.input.objectName,
|
objectType: step.settings.input.objectName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository: this.objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
case WorkflowActionType.FORM:
|
case WorkflowActionType.FORM:
|
||||||
return this.computeFormActionOutputSchema({
|
return this.computeFormActionOutputSchema({
|
||||||
formMetadata: step.settings.input,
|
formMetadata: step.settings.input,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository: this.objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
|
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
|
||||||
default:
|
default:
|
||||||
@ -95,11 +85,9 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
private async computeDatabaseEventTriggerOutputSchema({
|
private async computeDatabaseEventTriggerOutputSchema({
|
||||||
eventName,
|
eventName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository,
|
|
||||||
}: {
|
}: {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
}): Promise<OutputSchema> {
|
}): Promise<OutputSchema> {
|
||||||
const [nameSingular, action] = eventName.split('.');
|
const [nameSingular, action] = eventName.split('.');
|
||||||
|
|
||||||
@ -107,20 +95,14 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata = await objectMetadataRepository.findOneOrFail({
|
const objectMetadataInfo =
|
||||||
where: {
|
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||||
nameSingular,
|
nameSingular,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
);
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadata)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateFakeObjectRecordEvent(
|
return generateFakeObjectRecordEvent(
|
||||||
objectMetadata,
|
objectMetadataInfo,
|
||||||
action as DatabaseEventAction,
|
action as DatabaseEventAction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -128,16 +110,13 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
private async computeFindRecordsOutputSchema({
|
private async computeFindRecordsOutputSchema({
|
||||||
objectType,
|
objectType,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository,
|
|
||||||
}: {
|
}: {
|
||||||
objectType: string;
|
objectType: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
}): Promise<OutputSchema> {
|
}): Promise<OutputSchema> {
|
||||||
const recordOutputSchema = await this.computeRecordOutputSchema({
|
const recordOutputSchema = await this.computeRecordOutputSchema({
|
||||||
objectType,
|
objectType,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -159,25 +138,17 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
private async computeRecordOutputSchema({
|
private async computeRecordOutputSchema({
|
||||||
objectType,
|
objectType,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository,
|
|
||||||
}: {
|
}: {
|
||||||
objectType: string;
|
objectType: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
}): Promise<OutputSchema> {
|
}): Promise<OutputSchema> {
|
||||||
const objectMetadata = await objectMetadataRepository.findOneOrFail({
|
const objectMetadataInfo =
|
||||||
where: {
|
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||||
nameSingular: objectType,
|
objectType,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
);
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadata)) {
|
return generateFakeObjectRecord(objectMetadataInfo);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateFakeObjectRecord(objectMetadata);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeSendEmailActionOutputSchema(): OutputSchema {
|
private computeSendEmailActionOutputSchema(): OutputSchema {
|
||||||
@ -187,16 +158,18 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
private async computeFormActionOutputSchema({
|
private async computeFormActionOutputSchema({
|
||||||
formMetadata,
|
formMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataRepository,
|
|
||||||
}: {
|
}: {
|
||||||
formMetadata: FormFieldMetadata[];
|
formMetadata: FormFieldMetadata[];
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
}): Promise<OutputSchema> {
|
}): Promise<OutputSchema> {
|
||||||
|
const objectMetadataMaps =
|
||||||
|
await this.workflowCommonWorkspaceService.getObjectMetadataMaps(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
return generateFakeFormResponse({
|
return generateFakeFormResponse({
|
||||||
formMetadata,
|
formMetadata,
|
||||||
workspaceId,
|
objectMetadataMaps,
|
||||||
objectMetadataRepository,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,9 +7,14 @@ import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trig
|
|||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { CronTriggerCronCommand } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/commands/cron-trigger.cron.command';
|
import { CronTriggerCronCommand } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/commands/cron-trigger.cron.command';
|
||||||
import { CronTriggerCronJob } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/jobs/cron-trigger.cron.job';
|
import { CronTriggerCronJob } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/jobs/cron-trigger.cron.job';
|
||||||
|
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FeatureFlagModule, TypeOrmModule.forFeature([Workspace], 'core')],
|
imports: [
|
||||||
|
FeatureFlagModule,
|
||||||
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
|
WorkflowCommonModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AutomatedTriggerWorkspaceService,
|
AutomatedTriggerWorkspaceService,
|
||||||
DatabaseEventTriggerListener,
|
DatabaseEventTriggerListener,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
||||||
@ -17,6 +19,8 @@ import {
|
|||||||
WorkflowTriggerJob,
|
WorkflowTriggerJob,
|
||||||
WorkflowTriggerJobData,
|
WorkflowTriggerJobData,
|
||||||
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job';
|
} from 'src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job';
|
||||||
|
import { ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event';
|
||||||
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
AutomatedTriggerType,
|
AutomatedTriggerType,
|
||||||
WorkflowAutomatedTriggerWorkspaceEntity,
|
WorkflowAutomatedTriggerWorkspaceEntity,
|
||||||
@ -31,43 +35,172 @@ export class DatabaseEventTriggerListener {
|
|||||||
@InjectMessageQueue(MessageQueue.workflowQueue)
|
@InjectMessageQueue(MessageQueue.workflowQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
private readonly isFeatureFlagEnabledService: FeatureFlagService,
|
private readonly isFeatureFlagEnabledService: FeatureFlagService,
|
||||||
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@OnDatabaseBatchEvent('*', DatabaseEventAction.CREATED)
|
@OnDatabaseBatchEvent('*', DatabaseEventAction.CREATED)
|
||||||
async handleObjectRecordCreateEvent(
|
async handleObjectRecordCreateEvent(
|
||||||
payload: WorkspaceEventBatch<ObjectRecordCreateEvent>,
|
payload: WorkspaceEventBatch<ObjectRecordCreateEvent>,
|
||||||
) {
|
) {
|
||||||
await this.handleEvent(payload);
|
if (await this.shouldIgnoreEvent(payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedPayload = structuredClone(payload);
|
||||||
|
|
||||||
|
await this.enrichCreatedEvent(clonedPayload);
|
||||||
|
await this.handleEvent(clonedPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnDatabaseBatchEvent('*', DatabaseEventAction.UPDATED)
|
@OnDatabaseBatchEvent('*', DatabaseEventAction.UPDATED)
|
||||||
async handleObjectRecordUpdateEvent(
|
async handleObjectRecordUpdateEvent(
|
||||||
payload: WorkspaceEventBatch<ObjectRecordUpdateEvent>,
|
payload: WorkspaceEventBatch<ObjectRecordUpdateEvent>,
|
||||||
) {
|
) {
|
||||||
await this.handleEvent(payload);
|
if (await this.shouldIgnoreEvent(payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedPayload = structuredClone(payload);
|
||||||
|
|
||||||
|
await this.enrichUpdatedEvent(clonedPayload);
|
||||||
|
await this.handleEvent(clonedPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnDatabaseBatchEvent('*', DatabaseEventAction.DELETED)
|
@OnDatabaseBatchEvent('*', DatabaseEventAction.DELETED)
|
||||||
async handleObjectRecordDeleteEvent(
|
async handleObjectRecordDeleteEvent(
|
||||||
payload: WorkspaceEventBatch<ObjectRecordDeleteEvent>,
|
payload: WorkspaceEventBatch<ObjectRecordDeleteEvent>,
|
||||||
) {
|
) {
|
||||||
await this.handleEvent(payload);
|
if (await this.shouldIgnoreEvent(payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedPayload = structuredClone(payload);
|
||||||
|
|
||||||
|
await this.enrichDeletedEvent(clonedPayload);
|
||||||
|
await this.handleEvent(clonedPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnDatabaseBatchEvent('*', DatabaseEventAction.DESTROYED)
|
@OnDatabaseBatchEvent('*', DatabaseEventAction.DESTROYED)
|
||||||
async handleObjectRecordDestroyEvent(
|
async handleObjectRecordDestroyEvent(
|
||||||
payload: WorkspaceEventBatch<ObjectRecordDestroyEvent>,
|
payload: WorkspaceEventBatch<ObjectRecordDestroyEvent>,
|
||||||
) {
|
) {
|
||||||
await this.handleEvent(payload);
|
if (await this.shouldIgnoreEvent(payload)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedPayload = structuredClone(payload);
|
||||||
|
|
||||||
|
await this.enrichDestroyedEvent(clonedPayload);
|
||||||
|
await this.handleEvent(clonedPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleEvent(
|
private async enrichCreatedEvent(
|
||||||
payload: WorkspaceEventBatch<
|
payload: WorkspaceEventBatch<ObjectRecordCreateEvent>,
|
||||||
| ObjectRecordCreateEvent
|
) {
|
||||||
| ObjectRecordUpdateEvent
|
const workspaceId = payload.workspaceId;
|
||||||
| ObjectRecordDeleteEvent
|
|
||||||
| ObjectRecordDestroyEvent
|
for (const event of payload.events) {
|
||||||
>,
|
await this.enrichRecord({
|
||||||
|
event,
|
||||||
|
record: event.properties.after,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichUpdatedEvent(
|
||||||
|
payload: WorkspaceEventBatch<ObjectRecordUpdateEvent>,
|
||||||
|
) {
|
||||||
|
const workspaceId = payload.workspaceId;
|
||||||
|
|
||||||
|
for (const event of payload.events) {
|
||||||
|
await this.enrichRecord({
|
||||||
|
event,
|
||||||
|
record: event.properties.before,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
await this.enrichRecord({
|
||||||
|
event,
|
||||||
|
record: event.properties.after,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichDeletedEvent(
|
||||||
|
payload: WorkspaceEventBatch<ObjectRecordDeleteEvent>,
|
||||||
|
) {
|
||||||
|
const workspaceId = payload.workspaceId;
|
||||||
|
|
||||||
|
for (const event of payload.events) {
|
||||||
|
await this.enrichRecord({
|
||||||
|
event,
|
||||||
|
record: event.properties.before,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichDestroyedEvent(
|
||||||
|
payload: WorkspaceEventBatch<ObjectRecordDestroyEvent>,
|
||||||
|
) {
|
||||||
|
const workspaceId = payload.workspaceId;
|
||||||
|
|
||||||
|
for (const event of payload.events) {
|
||||||
|
await this.enrichRecord({
|
||||||
|
event,
|
||||||
|
record: event.properties.before,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async enrichRecord({
|
||||||
|
event,
|
||||||
|
record,
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
event: ObjectRecordNonDestructiveEvent;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
record: Record<string, any>;
|
||||||
|
workspaceId: string;
|
||||||
|
}) {
|
||||||
|
const { objectMetadataMaps, objectMetadataItemWithFieldsMaps } =
|
||||||
|
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||||
|
event.objectMetadata.nameSingular,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldsByJoinColumnName =
|
||||||
|
objectMetadataItemWithFieldsMaps.fieldsByJoinColumnName;
|
||||||
|
|
||||||
|
for (const [joinColumn, joinField] of Object.entries(
|
||||||
|
fieldsByJoinColumnName,
|
||||||
|
)) {
|
||||||
|
const joinRecordId = record[joinColumn];
|
||||||
|
const relatedObjectMetadataId = joinField.relationTargetObjectMetadataId;
|
||||||
|
|
||||||
|
if (!isDefined(relatedObjectMetadataId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relatedObjectMetadataNameSingular =
|
||||||
|
objectMetadataMaps.byId[relatedObjectMetadataId].nameSingular;
|
||||||
|
|
||||||
|
const relatedObjectRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
relatedObjectMetadataNameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
record[joinField.name] = await relatedObjectRepository.findOne({
|
||||||
|
where: { id: joinRecordId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async shouldIgnoreEvent(
|
||||||
|
payload: WorkspaceEventBatch<ObjectRecordNonDestructiveEvent>,
|
||||||
) {
|
) {
|
||||||
const workspaceId = payload.workspaceId;
|
const workspaceId = payload.workspaceId;
|
||||||
const databaseEventName = payload.name;
|
const databaseEventName = payload.name;
|
||||||
@ -79,7 +212,7 @@ export class DatabaseEventTriggerListener {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isWorkflowEnabled =
|
const isWorkflowEnabled =
|
||||||
@ -88,9 +221,14 @@ export class DatabaseEventTriggerListener {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isWorkflowEnabled) {
|
return !isWorkflowEnabled;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
private async handleEvent(
|
||||||
|
payload: WorkspaceEventBatch<ObjectRecordNonDestructiveEvent>,
|
||||||
|
) {
|
||||||
|
const workspaceId = payload.workspaceId;
|
||||||
|
const databaseEventName = payload.name;
|
||||||
|
|
||||||
const workflowAutomatedTriggerRepository =
|
const workflowAutomatedTriggerRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||||
@ -107,7 +245,7 @@ export class DatabaseEventTriggerListener {
|
|||||||
|
|
||||||
for (const eventListener of eventListeners) {
|
for (const eventListener of eventListeners) {
|
||||||
for (const eventPayload of payload.events) {
|
for (const eventPayload of payload.events) {
|
||||||
this.messageQueueService.add<WorkflowTriggerJobData>(
|
await this.messageQueueService.add<WorkflowTriggerJobData>(
|
||||||
WorkflowTriggerJob.name,
|
WorkflowTriggerJob.name,
|
||||||
{
|
{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
Reference in New Issue
Block a user