create groupBy resolver + mock groupBy service (#14530)

To test :

```
query : 
query companiesGroupBy($groupBy: CompanyGroupByInput){
  companiesGroupBy(groupBy: $groupBy){
    groupByDimensionValues
    countEmptyId
    sumAnnualRecurringRevenueAmountMicros
    countEmptyAddress
    percentageEmptySearchVector
  }
}

input : 
{"groupBy": {"name": true, "employees": true, "domainName": {"primaryLinkUrl": true}}}

response : 
{
  "data": {
    "companiesGroupBy": [
      {
        "groupByDimensionValues": [
          "Text 1",
          "twenty.com",
          "1"
        ],
        "countEmptyId": 39,
        "sumAnnualRecurringRevenueAmountMicros": 20,
        "countEmptyAddress": 56,
        "percentageEmptySearchVector": 88
      },
      {
        "groupByDimensionValues": [
          "Long long long long and long text 2",
          "github.com/twentyhq/twenty",
          "2"
        ],
        "countEmptyId": 20,
        "sumAnnualRecurringRevenueAmountMicros": 56,
        "countEmptyAddress": 88,
        "percentageEmptySearchVector": 2
      },
      {
        "groupByDimensionValues": [
          "Text 3",
          "react.dev",
          "3"
        ],
        "countEmptyId": 56,
        "sumAnnualRecurringRevenueAmountMicros": 88,
        "countEmptyAddress": 2,
        "percentageEmptySearchVector": 39
      }
    ]
  }
}
```
closes https://github.com/twentyhq/core-team-issues/issues/1439
This commit is contained in:
Etienne
2025-09-17 10:23:34 +02:00
committed by GitHub
parent 2430f21d83
commit 61b9d82950
31 changed files with 1091 additions and 232 deletions
@@ -2,6 +2,7 @@ export const RESOLVER_METHOD_NAMES = {
FIND_MANY: 'findMany',
FIND_ONE: 'findOne',
FIND_DUPLICATES: 'findDuplicates',
GROUP_BY: 'groupBy',
CREATE_MANY: 'createMany',
CREATE_ONE: 'createOne',
UPDATE_MANY: 'updateMany',
@@ -1,5 +1,6 @@
import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory';
import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory';
import { GroupByResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory';
import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory';
import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory';
import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory';
@@ -29,6 +30,7 @@ export const workspaceResolverBuilderFactories = [
RestoreOneResolverFactory,
RestoreManyResolverFactory,
MergeManyResolverFactory,
GroupByResolverFactory,
];
export const workspaceResolverBuilderMethodNames = {
@@ -36,6 +38,7 @@ export const workspaceResolverBuilderMethodNames = {
FindManyResolverFactory.methodName,
FindOneResolverFactory.methodName,
FindDuplicatesResolverFactory.methodName,
GroupByResolverFactory.methodName,
],
mutations: [
CreateManyResolverFactory.methodName,
@@ -0,0 +1,45 @@
import { Injectable } from '@nestjs/common';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
GroupByResolverArgs,
type Resolver,
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQueryGroupByResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-group-by-resolver.service';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class GroupByResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
public static methodName = RESOLVER_METHOD_NAMES.GROUP_BY;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQueryGroupByResolverService,
) {}
create(
context: WorkspaceSchemaBuilderContext,
): Resolver<GroupByResolverArgs> {
const internalContext = context;
return async (_source, args, _context, info) => {
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
GroupByResolverFactory.methodName,
);
};
}
}
@@ -3,6 +3,7 @@ import { type GraphQLFieldResolver } from 'graphql';
import {
type ObjectRecord,
type ObjectRecordFilter,
type ObjectRecordGroupBy,
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
@@ -64,6 +65,13 @@ export interface CreateManyResolverArgs<
upsert?: boolean;
}
export interface GroupByResolverArgs<Filter = ObjectRecordFilter> {
filter?: Filter;
groupBy: ObjectRecordGroupBy;
viewId?: string;
orderBy?: ObjectRecordOrderBy;
}
export interface UpdateOneResolverArgs<
Data extends Partial<ObjectRecord> = Partial<ObjectRecord>,
> {
@@ -131,6 +139,7 @@ export interface WorkspaceResolverBuilderMethods {
export type ResolverArgs =
| CreateManyResolverArgs
| CreateOneResolverArgs
| GroupByResolverArgs
| DeleteManyResolverArgs
| DeleteOneResolverArgs
| DestroyManyResolverArgs
@@ -6,6 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { DeleteManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory';
import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory';
import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory';
import { GroupByResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/group-by-resolver.factory';
import { MergeManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/merge-many-resolver.factory';
import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory';
import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory';
@@ -54,6 +55,7 @@ export class WorkspaceResolverFactory {
private readonly restoreManyResolverFactory: RestoreManyResolverFactory,
private readonly destroyManyResolverFactory: DestroyManyResolverFactory,
private readonly mergeManyResolverFactory: MergeManyResolverFactory,
private readonly groupByResolverFactory: GroupByResolverFactory,
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
private readonly featureFlagService: FeatureFlagService,
) {}
@@ -81,6 +83,7 @@ export class WorkspaceResolverFactory {
['restoreOne', this.restoreOneResolverFactory],
['updateMany', this.updateManyResolverFactory],
['updateOne', this.updateOneResolverFactory],
['groupBy', this.groupByResolverFactory],
]);
const resolvers: IResolvers = {
Query: {},