[groupBy] Load record within groups (without relations) (#15307)
First step of https://github.com/twentyhq/core-team-issues/issues/1726. I will handle relations in another PR. Another ticket is planned to allow for sorting among the records. When querying records we have set the number of maximum groups to 50, and of maximum records par group to 10. <img width="1283" height="798" alt="image" src="https://github.com/user-attachments/assets/7ffc9805-d715-4017-8030-4f3521e6f741" />
This commit is contained in:
+31
-7
@@ -6,6 +6,7 @@ import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-qu
|
||||
import { parseAggregateFieldsRestRequest } from 'src/engine/api/rest/input-request-parsers/aggregate-fields-parser-utils/parse-aggregate-fields-rest-request.util';
|
||||
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
|
||||
import { parseGroupByRestRequest } from 'src/engine/api/rest/input-request-parsers/group-by-parser-utils/parse-group-by-rest-request.util';
|
||||
import { parseIncludeRecordsSampleRestRequest } from 'src/engine/api/rest/input-request-parsers/group-by-with-records/parse-include-records-sample-rest-request.util';
|
||||
import { parseOrderByWithGroupByRestRequest } from 'src/engine/api/rest/input-request-parsers/order-by-with-group-by-parser-utils/parse-order-by-with-group-by-rest-request.util';
|
||||
import { parseViewIdRestRequest } from 'src/engine/api/rest/input-request-parsers/view-id-parser-utils/parse-view-id-rest-request.util';
|
||||
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
@@ -22,13 +23,16 @@ export class RestApiGroupByHandler extends RestApiBaseHandler {
|
||||
async handle(request: AuthenticatedRequest) {
|
||||
try {
|
||||
const {
|
||||
filter,
|
||||
orderBy,
|
||||
viewId,
|
||||
groupBy,
|
||||
selectedFields,
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
} = await this.buildCommonOptions(request);
|
||||
|
||||
const { filter, orderBy, viewId, groupBy, selectedFields } =
|
||||
this.parseRequestArgs(request);
|
||||
objectMetadataItemWithFieldMaps,
|
||||
includeRecords,
|
||||
} = await this.parseRequestArgs(request);
|
||||
|
||||
return await this.commonGroupByQueryRunnerService.execute(
|
||||
{
|
||||
@@ -37,6 +41,7 @@ export class RestApiGroupByHandler extends RestApiBaseHandler {
|
||||
viewId,
|
||||
groupBy,
|
||||
selectedFields,
|
||||
includeRecords,
|
||||
},
|
||||
{
|
||||
authContext,
|
||||
@@ -49,20 +54,39 @@ export class RestApiGroupByHandler extends RestApiBaseHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private parseRequestArgs(request: AuthenticatedRequest) {
|
||||
private async parseRequestArgs(request: AuthenticatedRequest) {
|
||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||
await this.buildCommonOptions(request);
|
||||
|
||||
const orderByWithGroupBy = parseOrderByWithGroupByRestRequest(request);
|
||||
const filter = parseFilterRestRequest(request);
|
||||
const viewId = parseViewIdRestRequest(request);
|
||||
const groupBy = parseGroupByRestRequest(request);
|
||||
const includeRecords = parseIncludeRecordsSampleRestRequest(request);
|
||||
const aggregateFields = parseAggregateFieldsRestRequest(request);
|
||||
const selectedFields = { ...aggregateFields, groupByDimensionValues: true };
|
||||
let selectedFields = { ...aggregateFields, groupByDimensionValues: true };
|
||||
|
||||
if (includeRecords) {
|
||||
const selectableFields = await this.computeSelectedFields({
|
||||
depth: 0,
|
||||
objectMetadataMapItem: objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
authContext,
|
||||
});
|
||||
|
||||
selectedFields = { ...selectedFields, ...selectableFields };
|
||||
}
|
||||
|
||||
return {
|
||||
authContext,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
filter,
|
||||
orderBy: orderByWithGroupBy,
|
||||
viewId,
|
||||
groupBy,
|
||||
selectedFields,
|
||||
includeRecords,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace
|
||||
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type';
|
||||
import { CommonSelectedFields } from 'src/engine/api/common/types/common-selected-fields-result.type';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
||||
@@ -596,7 +597,7 @@ export abstract class RestApiBaseHandler {
|
||||
depth?: Depth | undefined;
|
||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
}) {
|
||||
}): Promise<CommonSelectedFields> {
|
||||
const { objectsPermissions } =
|
||||
await this.getObjectsPermissions(authContext);
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ export const parseCorePath = (
|
||||
|
||||
if (
|
||||
queryAction[1] === 'duplicates' ||
|
||||
queryAction[1] === 'group' ||
|
||||
queryAction[1] === 'groupBy' ||
|
||||
queryAction[1] === 'merge'
|
||||
) {
|
||||
return { object: queryAction[0] };
|
||||
|
||||
@@ -122,15 +122,7 @@ export class RestApiCoreService {
|
||||
}
|
||||
|
||||
async groupBy(request: AuthenticatedRequest) {
|
||||
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
|
||||
|
||||
if (isCommonApiEnabled) {
|
||||
return await this.restApiGroupByHandler.handle(request);
|
||||
} else {
|
||||
throw new BadRequestException(
|
||||
'Activate feature flag to use GroupBy in the REST API',
|
||||
);
|
||||
}
|
||||
return await this.restApiGroupByHandler.handle(request);
|
||||
}
|
||||
|
||||
async delete(request: AuthenticatedRequest) {
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
|
||||
|
||||
export const parseIncludeRecordsSampleRestRequest = (
|
||||
request: AuthenticatedRequest,
|
||||
): boolean => {
|
||||
if (!isDefined(request.query.include_records_sample)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return request.query.include_records_sample === 'true';
|
||||
};
|
||||
Reference in New Issue
Block a user