CreateMany optim - .save -> .updateMany + position (#13704)
fixes : https://github.com/twentyhq/core-team-issues/issues/1312 --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+46
-19
@@ -6,16 +6,27 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu
|
||||
import { ResolverArgsType } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||
import {
|
||||
RecordPositionService,
|
||||
RecordPositionServiceCreateArgs,
|
||||
} from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
|
||||
describe('QueryRunnerArgsFactory', () => {
|
||||
const recordPositionService = {
|
||||
buildRecordPosition: jest.fn().mockResolvedValue(2),
|
||||
overridePositionOnRecords: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
({ partialRecordInputs }: { partialRecordInputs: any[] }) => {
|
||||
return Promise.resolve(
|
||||
partialRecordInputs.map((record: any) => ({
|
||||
...record,
|
||||
position:
|
||||
record.position === 'last' || !record.position
|
||||
? 2
|
||||
: record.position,
|
||||
})),
|
||||
);
|
||||
},
|
||||
),
|
||||
};
|
||||
const workspaceId = 'workspaceId';
|
||||
const options = {
|
||||
@@ -96,16 +107,24 @@ describe('QueryRunnerArgsFactory', () => {
|
||||
ResolverArgsType.CreateMany,
|
||||
);
|
||||
|
||||
const expectedArgs: RecordPositionServiceCreateArgs = {
|
||||
value: 'last',
|
||||
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
||||
const expectedArgs = {
|
||||
partialRecordInputs: [{ position: 'last', testNumber: 1 }],
|
||||
objectMetadata: {
|
||||
isCustom: true,
|
||||
nameSingular: 'testNumber',
|
||||
fieldIdByName: {
|
||||
position: 'position-id',
|
||||
testNumber: 'testNumber-id',
|
||||
otherField: 'otherField-id',
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
index: 0,
|
||||
shouldBackfillPositionIfUndefined: true,
|
||||
};
|
||||
|
||||
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledWith(
|
||||
expectedArgs,
|
||||
);
|
||||
expect(
|
||||
recordPositionService.overridePositionOnRecords,
|
||||
).toHaveBeenCalledWith(expectedArgs);
|
||||
expect(result).toEqual({
|
||||
id: 'uuid',
|
||||
data: [{ position: 2, testNumber: 1 }],
|
||||
@@ -124,16 +143,24 @@ describe('QueryRunnerArgsFactory', () => {
|
||||
ResolverArgsType.CreateMany,
|
||||
);
|
||||
|
||||
const expectedArgs: RecordPositionServiceCreateArgs = {
|
||||
value: 'first',
|
||||
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
||||
const expectedArgs = {
|
||||
partialRecordInputs: [{ testNumber: 1 }],
|
||||
objectMetadata: {
|
||||
isCustom: true,
|
||||
nameSingular: 'testNumber',
|
||||
fieldIdByName: {
|
||||
position: 'position-id',
|
||||
testNumber: 'testNumber-id',
|
||||
otherField: 'otherField-id',
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
index: 0,
|
||||
shouldBackfillPositionIfUndefined: true,
|
||||
};
|
||||
|
||||
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledWith(
|
||||
expectedArgs,
|
||||
);
|
||||
expect(
|
||||
recordPositionService.overridePositionOnRecords,
|
||||
).toHaveBeenCalledWith(expectedArgs);
|
||||
expect(result).toEqual({
|
||||
id: 'uuid',
|
||||
data: [{ position: 2, testNumber: 1 }],
|
||||
|
||||
+76
-126
@@ -27,11 +27,6 @@ import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.
|
||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
type ArgPositionBackfillInput = {
|
||||
argIndex?: number;
|
||||
shouldBackfillPosition: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class QueryRunnerArgsFactory {
|
||||
constructor(
|
||||
@@ -47,50 +42,36 @@ export class QueryRunnerArgsFactory {
|
||||
const fieldMetadataMapByNameByName =
|
||||
options.objectMetadataItemWithFieldMaps.fieldsById;
|
||||
|
||||
const shouldBackfillPosition = Object.values(
|
||||
options.objectMetadataItemWithFieldMaps.fieldsById,
|
||||
).some(
|
||||
(field) =>
|
||||
field.type === FieldMetadataType.POSITION && field.name === 'position',
|
||||
);
|
||||
|
||||
switch (resolverArgsType) {
|
||||
case ResolverArgsType.CreateOne:
|
||||
return {
|
||||
...args,
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as CreateOneResolverArgs).data,
|
||||
options,
|
||||
{
|
||||
argIndex: 0,
|
||||
shouldBackfillPosition,
|
||||
},
|
||||
),
|
||||
data: (
|
||||
await this.overrideDataByFieldMetadata(
|
||||
[(args as CreateOneResolverArgs).data],
|
||||
options,
|
||||
)
|
||||
)[0],
|
||||
} satisfies CreateOneResolverArgs;
|
||||
case ResolverArgsType.CreateMany:
|
||||
return {
|
||||
...args,
|
||||
data: await Promise.all(
|
||||
(args as CreateManyResolverArgs).data?.map((arg, index) =>
|
||||
this.overrideDataByFieldMetadata(arg, options, {
|
||||
argIndex: index,
|
||||
shouldBackfillPosition,
|
||||
}),
|
||||
) ?? [],
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as CreateManyResolverArgs).data,
|
||||
options,
|
||||
),
|
||||
} satisfies CreateManyResolverArgs;
|
||||
case ResolverArgsType.UpdateOne:
|
||||
return {
|
||||
...args,
|
||||
id: (args as UpdateOneResolverArgs).id,
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as UpdateOneResolverArgs).data,
|
||||
options,
|
||||
{
|
||||
argIndex: 0,
|
||||
shouldBackfillPosition: false,
|
||||
},
|
||||
),
|
||||
data: (
|
||||
await this.overrideDataByFieldMetadata(
|
||||
[(args as UpdateOneResolverArgs).data],
|
||||
options,
|
||||
false,
|
||||
)
|
||||
)[0],
|
||||
} satisfies UpdateOneResolverArgs;
|
||||
case ResolverArgsType.UpdateMany:
|
||||
return {
|
||||
@@ -99,14 +80,13 @@ export class QueryRunnerArgsFactory {
|
||||
(args as UpdateManyResolverArgs).filter,
|
||||
options.objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as UpdateManyResolverArgs).data,
|
||||
options,
|
||||
{
|
||||
argIndex: 0,
|
||||
shouldBackfillPosition: false,
|
||||
},
|
||||
),
|
||||
data: (
|
||||
await this.overrideDataByFieldMetadata(
|
||||
[(args as UpdateManyResolverArgs).data],
|
||||
options,
|
||||
false,
|
||||
)
|
||||
)[0],
|
||||
} satisfies UpdateManyResolverArgs;
|
||||
case ResolverArgsType.FindOne:
|
||||
return {
|
||||
@@ -137,13 +117,10 @@ export class QueryRunnerArgsFactory {
|
||||
),
|
||||
) ?? [],
|
||||
)) as string[],
|
||||
data: await Promise.all(
|
||||
(args as FindDuplicatesResolverArgs).data?.map((arg, index) =>
|
||||
this.overrideDataByFieldMetadata(arg, options, {
|
||||
argIndex: index,
|
||||
shouldBackfillPosition,
|
||||
}),
|
||||
) ?? [],
|
||||
data: await this.overrideDataByFieldMetadata(
|
||||
(args as FindDuplicatesResolverArgs).data,
|
||||
options,
|
||||
false,
|
||||
),
|
||||
} satisfies FindDuplicatesResolverArgs;
|
||||
case ResolverArgsType.MergeMany:
|
||||
@@ -168,97 +145,70 @@ export class QueryRunnerArgsFactory {
|
||||
}
|
||||
|
||||
private async overrideDataByFieldMetadata(
|
||||
data: Partial<ObjectRecord> | undefined,
|
||||
partialRecordInputs: Partial<ObjectRecord>[] | undefined,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
argPositionBackfillInput: ArgPositionBackfillInput,
|
||||
): Promise<Partial<ObjectRecord>> {
|
||||
if (!isDefined(data)) {
|
||||
return Promise.resolve({});
|
||||
shouldBackfillPositionIfUndefined = true,
|
||||
): Promise<Partial<ObjectRecord>[]> {
|
||||
if (!isDefined(partialRecordInputs)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const allOverriddenRecords: Partial<ObjectRecord>[] = [];
|
||||
|
||||
const workspace = options.authContext.workspace;
|
||||
|
||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
||||
|
||||
let isFieldPositionPresent = false;
|
||||
const overriddenPositionRecords =
|
||||
await this.recordPositionService.overridePositionOnRecords({
|
||||
partialRecordInputs,
|
||||
workspaceId: workspace.id,
|
||||
objectMetadata: {
|
||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular: options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
fieldIdByName: options.objectMetadataItemWithFieldMaps.fieldIdByName,
|
||||
},
|
||||
shouldBackfillPositionIfUndefined,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const createArgByArgKeyPromises: Promise<[string, any]>[] = Object.entries(
|
||||
data,
|
||||
for (const record of overriddenPositionRecords) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
).map(async ([key, value]): Promise<[string, any]> => {
|
||||
const fieldMetadataId =
|
||||
options.objectMetadataItemWithFieldMaps.fieldIdByName[key];
|
||||
const fieldMetadata =
|
||||
options.objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
const createArgByArgKey: [string, any][] = await Promise.all(
|
||||
Object.entries(record).map(async ([key, value]) => {
|
||||
const fieldMetadataId =
|
||||
options.objectMetadataItemWithFieldMaps.fieldIdByName[key];
|
||||
const fieldMetadata =
|
||||
options.objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||
|
||||
if (!fieldMetadata) {
|
||||
return [key, value];
|
||||
}
|
||||
if (!fieldMetadata) {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.POSITION: {
|
||||
isFieldPositionPresent = true;
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.NUMBER:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.PHONES:
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
case FieldMetadataType.LINKS:
|
||||
case FieldMetadataType.EMAILS: {
|
||||
const transformedValue =
|
||||
await this.recordInputTransformerService.transformFieldValue(
|
||||
fieldMetadata.type,
|
||||
value,
|
||||
);
|
||||
|
||||
const newValue = await this.recordPositionService.buildRecordPosition(
|
||||
{
|
||||
value,
|
||||
workspaceId: workspace.id,
|
||||
objectMetadata: {
|
||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular:
|
||||
options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
},
|
||||
index: argPositionBackfillInput.argIndex,
|
||||
},
|
||||
);
|
||||
return [key, transformedValue];
|
||||
}
|
||||
default:
|
||||
return [key, value];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return [key, newValue];
|
||||
}
|
||||
case FieldMetadataType.NUMBER:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.PHONES:
|
||||
case FieldMetadataType.RICH_TEXT_V2:
|
||||
case FieldMetadataType.LINKS:
|
||||
case FieldMetadataType.EMAILS: {
|
||||
const transformedValue =
|
||||
await this.recordInputTransformerService.transformFieldValue(
|
||||
fieldMetadata.type,
|
||||
value,
|
||||
);
|
||||
|
||||
return [key, transformedValue];
|
||||
}
|
||||
default:
|
||||
return [key, value];
|
||||
}
|
||||
});
|
||||
|
||||
const newArgEntries = await Promise.all(createArgByArgKeyPromises);
|
||||
|
||||
if (
|
||||
!isFieldPositionPresent &&
|
||||
argPositionBackfillInput.shouldBackfillPosition
|
||||
) {
|
||||
return Object.fromEntries([
|
||||
...newArgEntries,
|
||||
[
|
||||
'position',
|
||||
await this.recordPositionService.buildRecordPosition({
|
||||
value: 'first',
|
||||
workspaceId: workspace.id,
|
||||
objectMetadata: {
|
||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||
nameSingular:
|
||||
options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||
},
|
||||
index: argPositionBackfillInput.argIndex,
|
||||
}),
|
||||
],
|
||||
]);
|
||||
allOverriddenRecords.push(Object.fromEntries(createArgByArgKey));
|
||||
}
|
||||
|
||||
return Object.fromEntries(newArgEntries);
|
||||
return allOverriddenRecords;
|
||||
}
|
||||
|
||||
private overrideFilterByFieldMetadata(
|
||||
|
||||
Reference in New Issue
Block a user