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>
This commit is contained in:
@ -4,7 +4,7 @@ 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 { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.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';
|
||||
|
||||
@ -19,35 +19,40 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryCreateManyResolverService
|
||||
implements ResolverService<CreateManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<CreateManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, info, objectMetadataMap, objectMetadataMapItem } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
} = options;
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
@ -59,7 +64,7 @@ export class GraphqlQueryCreateManyResolverService
|
||||
});
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const nonFormattedUpsertedRecords = (await queryBuilder
|
||||
@ -71,42 +76,42 @@ export class GraphqlQueryCreateManyResolverService
|
||||
|
||||
const upsertedRecords = formatResult(
|
||||
nonFormattedUpsertedRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
upsertedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: upsertedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return upsertedRecords.map((record: ObjectRecord) =>
|
||||
return upsertedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord>(
|
||||
args: CreateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord>(
|
||||
args: CreateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
|
||||
args.data.forEach((record) => {
|
||||
if (record?.id) {
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.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 { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -16,46 +16,51 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyManyResolverService
|
||||
implements ResolverService<DestroyManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<DestroyManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: DestroyManyResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter,
|
||||
);
|
||||
|
||||
@ -66,31 +71,31 @@ export class GraphqlQueryDestroyManyResolverService
|
||||
|
||||
const deletedRecords = formatResult(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
deletedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: deletedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return deletedRecords.map((record: ObjectRecord) =>
|
||||
return deletedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.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 { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -20,45 +20,50 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyOneResolverService
|
||||
implements ResolverService<DestroyOneResolverArgs, IRecord>
|
||||
implements ResolverService<DestroyOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: DestroyOneResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
.where(`"${objectMetadataMapItem.nameSingular}".id = :id`, {
|
||||
.where(`"${objectMetadataItemWithFieldMaps.nameSingular}".id = :id`, {
|
||||
id: args.id,
|
||||
})
|
||||
.take(1)
|
||||
@ -75,30 +80,30 @@ export class GraphqlQueryDestroyOneResolverService
|
||||
|
||||
const recordBeforeDeletion = formatResult(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
)[0];
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
[recordBeforeDeletion],
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [recordBeforeDeletion],
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: recordBeforeDeletion,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
|
||||
@ -5,10 +5,10 @@ import { In } from 'typeorm';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -21,7 +21,7 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { settings } from 'src/engine/constants/settings';
|
||||
import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants';
|
||||
import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
@ -29,60 +29,63 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindDuplicatesResolverService
|
||||
implements
|
||||
ResolverService<FindDuplicatesResolverArgs, IConnection<IRecord>[]>
|
||||
ResolverService<FindDuplicatesResolverArgs, IConnection<ObjectRecord>[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: FindDuplicatesResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap } = options;
|
||||
): Promise<IConnection<T>[]> {
|
||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||
options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const existingRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const duplicateRecordsQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMap[objectMetadataMapItem.nameSingular].fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataMaps.byNameSingular[
|
||||
objectMetadataItemWithFieldMaps.nameSingular
|
||||
].fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||
let objectRecords: Partial<T>[] = [];
|
||||
|
||||
if (args.ids) {
|
||||
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
||||
.where({ id: In(args.ids) })
|
||||
.getMany()) as ObjectRecord[];
|
||||
.getMany()) as T[];
|
||||
|
||||
objectRecords = formatResult(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
} else if (args.data && !isEmpty(args.data)) {
|
||||
objectRecords = formatData(args.data, objectMetadataMapItem);
|
||||
objectRecords = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
}
|
||||
|
||||
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
||||
const duplicateConnections: IConnection<T>[] = await Promise.all(
|
||||
objectRecords.map(async (record) => {
|
||||
const duplicateConditions = this.buildDuplicateConditions(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
[record],
|
||||
record.id,
|
||||
);
|
||||
@ -90,7 +93,7 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
if (isEmpty(duplicateConditions)) {
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: [],
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 0,
|
||||
totalCount: 0,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -101,22 +104,22 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
duplicateRecordsQueryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
duplicateConditions,
|
||||
);
|
||||
|
||||
const nonFormattedDuplicates =
|
||||
(await withFilterQueryBuilder.getMany()) as ObjectRecord[];
|
||||
(await withFilterQueryBuilder.getMany()) as T[];
|
||||
|
||||
const duplicates = formatResult(
|
||||
nonFormattedDuplicates,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: duplicates,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: duplicates.length,
|
||||
totalCount: duplicates.length,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -130,16 +133,16 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
}
|
||||
|
||||
private buildDuplicateConditions(
|
||||
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||
records?: Partial<IRecord>[] | undefined,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
records?: Partial<ObjectRecord>[] | undefined,
|
||||
filteringByExistingRecordId?: string,
|
||||
): Partial<RecordFilter> {
|
||||
): Partial<ObjectRecordFilter> {
|
||||
if (!records || records.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const criteriaCollection = this.getApplicableDuplicateCriteriaCollection(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
const conditions = records.flatMap((record) => {
|
||||
@ -164,7 +167,7 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
});
|
||||
});
|
||||
|
||||
const filter: Partial<RecordFilter> = {};
|
||||
const filter: Partial<ObjectRecordFilter> = {};
|
||||
|
||||
if (conditions && !isEmpty(conditions)) {
|
||||
filter.or = conditions;
|
||||
@ -178,11 +181,12 @@ export class GraphqlQueryFindDuplicatesResolverService
|
||||
}
|
||||
|
||||
private getApplicableDuplicateCriteriaCollection(
|
||||
objectMetadataMapItem: ObjectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
) {
|
||||
return DUPLICATE_CRITERIA_COLLECTION.filter(
|
||||
(duplicateCriteria) =>
|
||||
duplicateCriteria.objectName === objectMetadataMapItem.nameSingular,
|
||||
duplicateCriteria.objectName ===
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
ObjectRecordOrderBy,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
RecordOrderBy,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -19,35 +18,45 @@ import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||
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 { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter';
|
||||
import {
|
||||
getCursor,
|
||||
getPaginationInfo,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindManyResolverService
|
||||
implements ResolverService<FindManyResolverArgs, IConnection<IRecord>>
|
||||
implements ResolverService<FindManyResolverArgs, IConnection<ObjectRecord>>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
OrderBy extends RecordOrderBy = RecordOrderBy,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
OrderBy extends ObjectRecordOrderBy = ObjectRecordOrderBy,
|
||||
>(
|
||||
args: FindManyResolverArgs<Filter, OrderBy>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||
options;
|
||||
): Promise<IConnection<T>> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -55,47 +64,43 @@ export class GraphqlQueryFindManyResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const countQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
const aggregateQueryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
countQueryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
selectedFields,
|
||||
);
|
||||
const isForwardPagination = !isDefined(args.before);
|
||||
|
||||
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const withDeletedCountQueryBuilder =
|
||||
graphqlQueryParser.applyDeletedAtToBuilder(
|
||||
withFilterCountQueryBuilder,
|
||||
const withFilterAggregateQueryBuilder =
|
||||
graphqlQueryParser.applyFilterToBuilder(
|
||||
aggregateQueryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const totalCount = isDefined(selectedFields.totalCount)
|
||||
? await withDeletedCountQueryBuilder.getCount()
|
||||
: 0;
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult =
|
||||
graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
const isForwardPagination = !isDefined(args.before);
|
||||
|
||||
const withDeletedAggregateQueryBuilder =
|
||||
graphqlQueryParser.applyDeletedAtToBuilder(
|
||||
withFilterAggregateQueryBuilder,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const cursor = getCursor(args);
|
||||
|
||||
@ -110,7 +115,7 @@ export class GraphqlQueryFindManyResolverService
|
||||
const cursorArgFilter = computeCursorArgFilter(
|
||||
cursor,
|
||||
orderByWithIdCondition,
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
@ -123,14 +128,14 @@ export class GraphqlQueryFindManyResolverService
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
appliedFilters,
|
||||
);
|
||||
|
||||
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
|
||||
withFilterQueryBuilder,
|
||||
orderByWithIdCondition,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
isForwardPagination,
|
||||
);
|
||||
|
||||
@ -139,14 +144,36 @@ export class GraphqlQueryFindManyResolverService
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
const isAggregationsEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsAggregateQueryEnabled,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
if (!isAggregationsEnabled) {
|
||||
graphqlQuerySelectedFieldsResult.aggregate = {
|
||||
totalCount: graphqlQuerySelectedFieldsResult.aggregate.totalCount,
|
||||
};
|
||||
}
|
||||
|
||||
const processAggregateHelper = new ProcessAggregateHelper();
|
||||
|
||||
processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
|
||||
fieldMetadataMapByName: objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
queryBuilder: withDeletedAggregateQueryBuilder,
|
||||
});
|
||||
|
||||
const limit = args.first ?? args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const nonFormattedObjectRecords = await withDeletedQueryBuilder
|
||||
.take(limit + 1)
|
||||
.getMany();
|
||||
|
||||
const objectRecords = formatResult(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||
@ -159,37 +186,42 @@ export class GraphqlQueryFindManyResolverService
|
||||
objectRecords.pop();
|
||||
}
|
||||
|
||||
const parentObjectRecordsAggregatedValues =
|
||||
await withDeletedAggregateQueryBuilder.getRawOne();
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
objectRecords,
|
||||
relations,
|
||||
if (graphqlQuerySelectedFieldsResult.relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: objectRecords,
|
||||
parentObjectRecordsAggregatedValues,
|
||||
relations: graphqlQuerySelectedFieldsResult.relations,
|
||||
aggregate: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
limit,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
const result = typeORMObjectRecordsParser.createConnection({
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectRecordsAggregatedValues: parentObjectRecordsAggregatedValues,
|
||||
selectedAggregatedFields: graphqlQuerySelectedFieldsResult.aggregate,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: limit,
|
||||
totalCount,
|
||||
totalCount: parentObjectRecordsAggregatedValues.totalCount,
|
||||
order: orderByWithIdCondition,
|
||||
hasNextPage,
|
||||
hasPreviousPage,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async validate<Filter extends RecordFilter>(
|
||||
async validate<Filter extends ObjectRecordFilter>(
|
||||
args: FindManyResolverArgs<Filter>,
|
||||
_options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
|
||||
@ -4,9 +4,9 @@ import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
} 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 { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -27,21 +27,25 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindOneResolverService
|
||||
implements ResolverService<FindOneResolverArgs, IRecord>
|
||||
implements ResolverService<FindOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
>(
|
||||
args: FindOneResolverArgs<Filter>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, info, objectMetadataMap } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
objectMetadataMaps,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -49,28 +53,28 @@ export class GraphqlQueryFindOneResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
@ -83,8 +87,8 @@ export class GraphqlQueryFindOneResolverService
|
||||
|
||||
const objectRecord = formatResult(
|
||||
nonFormattedObjectRecord,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (!objectRecord) {
|
||||
@ -99,29 +103,29 @@ export class GraphqlQueryFindOneResolverService
|
||||
const objectRecords = [objectRecord];
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
objectRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: objectRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: objectRecords[0],
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}) as ObjectRecord;
|
||||
}) as T;
|
||||
}
|
||||
|
||||
async validate<Filter extends RecordFilter>(
|
||||
async validate<Filter extends ObjectRecordFilter>(
|
||||
args: FindOneResolverArgs<Filter>,
|
||||
_options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
|
||||
@ -5,10 +5,10 @@ import { Brackets } from 'typeorm';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import {
|
||||
Record as IRecord,
|
||||
ObjectRecord,
|
||||
ObjectRecordFilter,
|
||||
OrderByDirection,
|
||||
RecordFilter,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
@ -22,40 +22,39 @@ import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQuerySearchResolverService
|
||||
implements ResolverService<SearchResolverArgs, IConnection<IRecord>>
|
||||
implements ResolverService<SearchResolverArgs, IConnection<ObjectRecord>>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<
|
||||
ObjectRecord extends IRecord = IRecord,
|
||||
Filter extends RecordFilter = RecordFilter,
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
Filter extends ObjectRecordFilter = ObjectRecordFilter,
|
||||
>(
|
||||
args: SearchResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<ObjectRecord>> {
|
||||
): Promise<IConnection<T>> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItem,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataMaps,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const repository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
authContext.workspace.id,
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
if (!isDefined(args.searchInput)) {
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: [],
|
||||
objectName: objectMetadataItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 0,
|
||||
totalCount: 0,
|
||||
order: [{ id: OrderByDirection.AscNullsFirst }],
|
||||
@ -69,16 +68,16 @@ export class GraphqlQuerySearchResolverService
|
||||
const limit = args?.limit ?? QUERY_MAX_RECORDS;
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
args.filter ?? ({} as Filter),
|
||||
);
|
||||
|
||||
@ -109,7 +108,7 @@ export class GraphqlQuerySearchResolverService
|
||||
.setParameter('searchTerms', searchTerms)
|
||||
.setParameter('searchTermsOr', searchTermsOr)
|
||||
.take(limit)
|
||||
.getMany()) as ObjectRecord[];
|
||||
.getMany()) as T[];
|
||||
|
||||
const objectRecords = await repository.formatResult(resultsWithTsVector);
|
||||
|
||||
@ -122,7 +121,7 @@ export class GraphqlQuerySearchResolverService
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: objectRecords ?? [],
|
||||
objectName: objectMetadataItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: limit,
|
||||
totalCount,
|
||||
order,
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.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 { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -20,18 +20,22 @@ import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryUpdateManyResolverService
|
||||
implements ResolverService<UpdateManyResolverArgs, IRecord[]>
|
||||
implements ResolverService<UpdateManyResolverArgs, ObjectRecord[]>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T[]> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -39,28 +43,28 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataMapItem.isCustom,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
|
||||
@ -69,7 +73,7 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
args.filter,
|
||||
);
|
||||
|
||||
const data = formatData(args.data, objectMetadataMapItem);
|
||||
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
|
||||
const nonFormattedUpdatedObjectRecords = await withFilterQueryBuilder
|
||||
.update(data)
|
||||
@ -78,42 +82,42 @@ export class GraphqlQueryUpdateManyResolverService
|
||||
|
||||
const updatedRecords = formatResult(
|
||||
nonFormattedUpdatedObjectRecords.raw,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
updatedRecords,
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: updatedRecords,
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return updatedRecords.map((record: ObjectRecord) =>
|
||||
return updatedRecords.map((record: T) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateManyResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
if (!args.filter) {
|
||||
throw new Error('Filter is required');
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import graphqlFields from 'graphql-fields';
|
||||
|
||||
import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface';
|
||||
import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.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 { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
@ -23,18 +23,22 @@ import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryUpdateOneResolverService
|
||||
implements ResolverService<UpdateOneResolverArgs, IRecord>
|
||||
implements ResolverService<UpdateOneResolverArgs, ObjectRecord>
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async resolve<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
async resolve<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<ObjectRecord> {
|
||||
const { authContext, objectMetadataMapItem, objectMetadataMap, info } =
|
||||
options;
|
||||
): Promise<T> {
|
||||
const {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
info,
|
||||
} = options;
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
@ -42,26 +46,26 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
);
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const graphqlQueryParser = new GraphqlQueryParser(
|
||||
objectMetadataMapItem.fields,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFields = graphqlFields(info);
|
||||
|
||||
const { relations } = graphqlQueryParser.parseSelectedFields(
|
||||
objectMetadataMapItem,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
selectedFields,
|
||||
);
|
||||
|
||||
const queryBuilder = repository.createQueryBuilder(
|
||||
objectMetadataMapItem.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const data = formatData(args.data, objectMetadataMapItem);
|
||||
const data = formatData(args.data, objectMetadataItemWithFieldMaps);
|
||||
|
||||
const result = await queryBuilder
|
||||
.update(data)
|
||||
@ -73,8 +77,8 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
|
||||
const updatedRecords = formatResult(
|
||||
nonFormattedUpdatedObjectRecords,
|
||||
objectMetadataMapItem,
|
||||
objectMetadataMap,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (updatedRecords.length === 0) {
|
||||
@ -84,38 +88,38 @@ export class GraphqlQueryUpdateOneResolverService
|
||||
);
|
||||
}
|
||||
|
||||
const updatedRecord = updatedRecords[0] as ObjectRecord;
|
||||
const updatedRecord = updatedRecords[0] as T;
|
||||
|
||||
const processNestedRelationsHelper = new ProcessNestedRelationsHelper();
|
||||
|
||||
if (relations) {
|
||||
await processNestedRelationsHelper.processNestedRelations(
|
||||
objectMetadataMap,
|
||||
objectMetadataMapItem,
|
||||
[updatedRecord],
|
||||
await processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [updatedRecord],
|
||||
relations,
|
||||
QUERY_MAX_RECORDS,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
dataSource,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord<ObjectRecord>({
|
||||
return typeORMObjectRecordsParser.processRecord<T>({
|
||||
objectRecord: updatedRecord,
|
||||
objectName: objectMetadataMapItem.nameSingular,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 1,
|
||||
totalCount: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async validate<ObjectRecord extends IRecord = IRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<ObjectRecord>>,
|
||||
async validate<T extends ObjectRecord = ObjectRecord>(
|
||||
args: UpdateOneResolverArgs<Partial<T>>,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<void> {
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataMapItem);
|
||||
assertMutationNotOnRemoteObject(options.objectMetadataItemWithFieldMaps);
|
||||
assertIsValidUuid(args.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user