[DASHBOARDS] Move all the graph computing logic to the backend (#17189)
- Create resolvers for each type of charts which needs data transformation after the group by operation: Bar Chart, Line Chart and Pie Chart - Move all the utils to the backend and refactored some into services This allows all the computation to be done in the backend, improving performances in the frontend.
This commit is contained in:
+232
@@ -0,0 +1,232 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { CalendarStartDay } from 'twenty-shared/constants';
|
||||
import {
|
||||
ObjectRecordGroupByDateGranularity,
|
||||
OrderByWithGroupBy,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
import { ObjectRecordGroupBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
|
||||
import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-group-by-query-runner.service';
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { GraphOrderBy } from 'src/engine/metadata-modules/page-layout-widget/enums/graph-order-by.enum';
|
||||
import { ChartFilter } from 'src/engine/metadata-modules/page-layout-widget/types/chart-filter.type';
|
||||
import { GRAPH_DEFAULT_DATE_GRANULARITY } from 'src/modules/dashboard/chart-data/constants/graph-default-date-granularity.constant';
|
||||
import { GRAPH_DEFAULT_ORDER_BY } from 'src/modules/dashboard/chart-data/constants/graph-default-order-by.constant';
|
||||
import { GroupByRawResult } from 'src/modules/dashboard/chart-data/types/group-by-raw-result.type';
|
||||
import { buildAggregateFieldKey } from 'src/modules/dashboard/chart-data/utils/build-aggregate-field-key.util';
|
||||
import {
|
||||
buildGroupByFieldObject,
|
||||
type GroupByFieldObject,
|
||||
} from 'src/modules/dashboard/chart-data/utils/build-group-by-field-object.util';
|
||||
import { convertChartFilterToGqlOperationFilter } from 'src/modules/dashboard/chart-data/utils/convert-chart-filter-to-gql-operation-filter.util';
|
||||
import { getFieldMetadata } from 'src/modules/dashboard/chart-data/utils/get-field-metadata.util';
|
||||
import { getGroupByOrderBy } from 'src/modules/dashboard/chart-data/utils/get-group-by-order-by.util';
|
||||
import { isRelationNestedFieldDateKind } from 'src/modules/dashboard/chart-data/utils/is-relation-nested-field-date-kind.util';
|
||||
import { transformAggregateValue } from 'src/modules/dashboard/chart-data/utils/transform-aggregate-value.util';
|
||||
|
||||
type ExecuteGroupByQueryParams = {
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
objectIdByNameSingular: Record<string, string>;
|
||||
authContext: AuthContext;
|
||||
groupByFieldMetadataId: string;
|
||||
groupBySubFieldName?: string | null;
|
||||
aggregateFieldMetadataId: string;
|
||||
aggregateOperation: AggregateOperations;
|
||||
filter?: ChartFilter;
|
||||
dateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
userTimezone: string;
|
||||
firstDayOfTheWeek: CalendarStartDay;
|
||||
limit: number;
|
||||
secondaryGroupByFieldMetadataId?: string;
|
||||
secondaryGroupBySubFieldName?: string | null;
|
||||
secondaryDateGranularity?: ObjectRecordGroupByDateGranularity;
|
||||
primaryAxisOrderBy?: GraphOrderBy;
|
||||
secondaryAxisOrderBy?: GraphOrderBy;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ChartDataQueryService {
|
||||
constructor(
|
||||
private readonly commonGroupByQueryRunnerService: CommonGroupByQueryRunnerService,
|
||||
) {}
|
||||
|
||||
async executeGroupByQuery({
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
authContext,
|
||||
groupByFieldMetadataId,
|
||||
groupBySubFieldName,
|
||||
aggregateFieldMetadataId,
|
||||
aggregateOperation,
|
||||
filter,
|
||||
dateGranularity,
|
||||
userTimezone,
|
||||
firstDayOfTheWeek,
|
||||
limit,
|
||||
primaryAxisOrderBy,
|
||||
secondaryGroupByFieldMetadataId,
|
||||
secondaryGroupBySubFieldName,
|
||||
secondaryDateGranularity,
|
||||
secondaryAxisOrderBy,
|
||||
}: ExecuteGroupByQueryParams): Promise<GroupByRawResult[]> {
|
||||
const gqlOperationFilter = convertChartFilterToGqlOperationFilter({
|
||||
filter,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
userTimezone,
|
||||
});
|
||||
|
||||
const primaryGroupByField = getFieldMetadata(
|
||||
groupByFieldMetadataId,
|
||||
flatFieldMetadataMaps.byId,
|
||||
);
|
||||
|
||||
const aggregateField = getFieldMetadata(
|
||||
aggregateFieldMetadataId,
|
||||
flatFieldMetadataMaps.byId,
|
||||
);
|
||||
|
||||
const isPrimaryFieldDate = isFieldMetadataDateKind(
|
||||
primaryGroupByField.type,
|
||||
);
|
||||
|
||||
const isPrimaryNestedDate = isRelationNestedFieldDateKind({
|
||||
relationFieldMetadata: primaryGroupByField,
|
||||
relationNestedFieldName: groupBySubFieldName ?? undefined,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const shouldApplyPrimaryDateGranularity =
|
||||
isPrimaryFieldDate || isPrimaryNestedDate;
|
||||
|
||||
const groupBy: GroupByFieldObject[] = [];
|
||||
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
fieldMetadata: primaryGroupByField,
|
||||
subFieldName: groupBySubFieldName,
|
||||
dateGranularity: shouldApplyPrimaryDateGranularity
|
||||
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
firstDayOfTheWeek,
|
||||
isNestedDateField: isPrimaryNestedDate,
|
||||
timeZone: userTimezone,
|
||||
}),
|
||||
);
|
||||
|
||||
const orderBy: OrderByWithGroupBy = [];
|
||||
|
||||
const primaryOrderBy = getGroupByOrderBy({
|
||||
graphOrderBy: primaryAxisOrderBy ?? GRAPH_DEFAULT_ORDER_BY,
|
||||
groupByFieldMetadata: primaryGroupByField,
|
||||
groupBySubFieldName,
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadata: aggregateField,
|
||||
dateGranularity: shouldApplyPrimaryDateGranularity
|
||||
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (isDefined(primaryOrderBy)) {
|
||||
orderBy.push(primaryOrderBy);
|
||||
}
|
||||
|
||||
if (isDefined(secondaryGroupByFieldMetadataId)) {
|
||||
const secondaryGroupByField = getFieldMetadata(
|
||||
secondaryGroupByFieldMetadataId,
|
||||
flatFieldMetadataMaps.byId,
|
||||
);
|
||||
|
||||
const isSecondaryFieldDate = isFieldMetadataDateKind(
|
||||
secondaryGroupByField.type,
|
||||
);
|
||||
const isSecondaryNestedDate = isRelationNestedFieldDateKind({
|
||||
relationFieldMetadata: secondaryGroupByField,
|
||||
relationNestedFieldName: secondaryGroupBySubFieldName ?? undefined,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const shouldApplySecondaryDateGranularity =
|
||||
isSecondaryFieldDate || isSecondaryNestedDate;
|
||||
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
fieldMetadata: secondaryGroupByField,
|
||||
subFieldName: secondaryGroupBySubFieldName,
|
||||
dateGranularity: shouldApplySecondaryDateGranularity
|
||||
? (secondaryDateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
firstDayOfTheWeek,
|
||||
isNestedDateField: isSecondaryNestedDate,
|
||||
timeZone: userTimezone,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isDefined(secondaryAxisOrderBy)) {
|
||||
const secondaryOrderByItem = getGroupByOrderBy({
|
||||
graphOrderBy: secondaryAxisOrderBy,
|
||||
groupByFieldMetadata: secondaryGroupByField,
|
||||
groupBySubFieldName: secondaryGroupBySubFieldName,
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadata: aggregateField,
|
||||
dateGranularity: shouldApplySecondaryDateGranularity
|
||||
? (secondaryDateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (isDefined(secondaryOrderByItem)) {
|
||||
orderBy.push(secondaryOrderByItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const aggregateFieldKey = buildAggregateFieldKey({
|
||||
aggregateOperation,
|
||||
aggregateFieldMetadata: aggregateField,
|
||||
});
|
||||
|
||||
const selectedFields = {
|
||||
[aggregateFieldKey]: true,
|
||||
groupByDimensionValues: true,
|
||||
};
|
||||
|
||||
const results = await this.commonGroupByQueryRunnerService.execute(
|
||||
{
|
||||
filter: gqlOperationFilter,
|
||||
orderBy: orderBy.length > 0 ? orderBy : undefined,
|
||||
groupBy: groupBy as ObjectRecordGroupBy,
|
||||
selectedFields,
|
||||
limit,
|
||||
},
|
||||
{
|
||||
authContext,
|
||||
flatObjectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectIdByNameSingular,
|
||||
},
|
||||
);
|
||||
|
||||
return results.map((result) => ({
|
||||
groupByDimensionValues: result.groupByDimensionValues ?? [],
|
||||
aggregateValue: transformAggregateValue({
|
||||
rawValue: result[aggregateFieldKey],
|
||||
aggregateFieldType: aggregateField.type,
|
||||
aggregateOperation,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user