a799370483
First step of https://github.com/twentyhq/twenty/issues/6868 Adds min.., max.. queries for DATETIME fields adds min.., max.., avg.., sum.. queries for NUMBER fields (count distinct operation and composite fields such as CURRENCY handling will be dealt with in a future PR) <img width="1422" alt="Capture d’écran 2024-11-06 à 15 48 46" src="https://github.com/user-attachments/assets/4bcdece0-ad3e-4536-9720-fe4044a36719"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
|
|
import graphqlFields from 'graphql-fields';
|
|
import { In, InsertResult } from 'typeorm';
|
|
|
|
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
|
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
|
|
|
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
|
|
@Injectable()
|
|
export class GraphqlQueryCreateManyResolverService
|
|
implements ResolverService<CreateManyResolverArgs, ObjectRecord[]>
|
|
{
|
|
constructor(
|
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
) {}
|
|
|
|
async resolve<T extends ObjectRecord = ObjectRecord>(
|
|
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
|
options: WorkspaceQueryRunnerOptions,
|
|
): Promise<T[]> {
|
|
const {
|
|
authContext,
|
|
info,
|
|
objectMetadataMaps,
|
|
objectMetadataItemWithFieldMaps,
|
|
} = options;
|
|
const dataSource =
|
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
|
authContext.workspace.id,
|
|
);
|
|
|
|
const repository = dataSource.getRepository(
|
|
objectMetadataItemWithFieldMaps.nameSingular,
|
|
);
|
|
|
|
const graphqlQueryParser = new GraphqlQueryParser(
|
|
objectMetadataItemWithFieldMaps.fieldsByName,
|
|
objectMetadataMaps,
|
|
);
|
|
|
|
const selectedFields = graphqlFields(info);
|
|
|
|
const { relations } = graphqlQueryParser.parseSelectedFields(
|
|
objectMetadataItemWithFieldMaps,
|
|
selectedFields,
|
|
);
|
|
|
|
const objectRecords: InsertResult = !args.upsert
|
|
? await repository.insert(args.data)
|
|
: await repository.upsert(args.data, {
|
|
conflictPaths: ['id'],
|
|
skipUpdateIfNoValuesChanged: true,
|
|
});
|
|
|
|
const queryBuilder = repository.createQueryBuilder(
|
|
objectMetadataItemWithFieldMaps.nameSingular,
|
|
);
|
|
|
|
const nonFormattedUpsertedRecords = (await queryBuilder
|
|
.where({
|
|
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
|
})
|
|
.take(QUERY_MAX_RECORDS)
|
|
.getMany()) as ObjectRecord[];
|
|
|
|
const upsertedRecords = formatResult(
|
|
nonFormattedUpsertedRecords,
|
|
objectMetadataItemWithFieldMaps,
|
|
objectMetadataMaps,
|
|
);
|
|
|
|
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
|
|
|
if (relations) {
|
|
await processNestedRelationsHelper.processNestedRelations({
|
|
objectMetadataMaps,
|
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
|
parentObjectRecords: upsertedRecords,
|
|
relations,
|
|
limit: QUERY_MAX_RECORDS,
|
|
authContext,
|
|
dataSource,
|
|
});
|
|
}
|
|
|
|
const typeORMObjectRecordsParser =
|
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
|
|
|
return upsertedRecords.map((record: T) =>
|
|
typeORMObjectRecordsParser.processRecord({
|
|
objectRecord: record,
|
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
|
take: 1,
|
|
totalCount: 1,
|
|
}),
|
|
);
|
|
}
|
|
|
|
async validate<T extends ObjectRecord>(
|
|
args: CreateManyResolverArgs<Partial<T>>,
|
|
options: WorkspaceQueryRunnerOptions,
|
|
): Promise<void> {
|
|
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
|
|
|
args.data.forEach((record) => {
|
|
if (record?.id) {
|
|
assertIsValidUuid(record.id);
|
|
}
|
|
});
|
|
}
|
|
}
|