[GroupBy] Allow sorting in bar chart (#15097)

Closes https://github.com/twentyhq/core-team-issues/issues/1628

From a technical perspective, we can add more ordering options, such as
the ability to combine two sorts on the X axis, e.g. sort by Close date
ASC and then by Sum ASC, which will sort groups that have the same close
date between themselves depending on their sum ASC. @Bonapara could you
provide design if you want this to be implemented (quite short on our
hand i think - maybe in V2 though)?


https://github.com/user-attachments/assets/6ef21fe1-9d8f-43c0-bfa2-f6fc6341cacf

---------

Co-authored-by: ehconitin <nitinkoche03@gmail.com>
This commit is contained in:
Marie
2025-10-15 14:11:14 +02:00
committed by GitHub
parent 436084f8f4
commit f65783f900
83 changed files with 453 additions and 247 deletions
@@ -54,6 +54,7 @@ export const useGraphWidgetGroupByQuery = ({
const variables = generateGroupByQueryVariablesFromBarChartConfiguration({
objectMetadataItem,
barChartConfiguration: configuration,
aggregateOperation,
});
const apolloCoreClient = useApolloCoreClient();
@@ -0,0 +1,114 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import {
ExtendedAggregateOperations,
GraphOrderBy,
GraphType,
type BarChartConfiguration,
} from '~/generated/graphql';
import { transformTwoDimensionalGroupByToBarChartData } from '../transformTwoDimensionalGroupByToBarChartData';
describe('transformTwoDimensionalGroupByToBarChartData', () => {
const mockGroupByFieldX = {
id: 'field-x',
name: 'createdAt',
type: FieldMetadataType.DATE,
label: 'Created At',
} as FieldMetadataItem;
const mockGroupByFieldY = {
id: 'field-y',
name: 'stage',
type: FieldMetadataType.SELECT,
label: 'Stage',
} as FieldMetadataItem;
const mockAggregateField = {
id: 'field-aggregate',
name: 'amount',
type: FieldMetadataType.NUMBER,
label: 'Amount',
} as FieldMetadataItem;
const mockObjectMetadataItem = {
id: 'object-1',
nameSingular: 'opportunity',
namePlural: 'opportunities',
fields: [mockGroupByFieldX, mockGroupByFieldY, mockAggregateField],
} as ObjectMetadataItem;
const mockConfiguration: BarChartConfiguration = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
aggregateFieldMetadataId: 'field-aggregate',
aggregateOperation: ExtendedAggregateOperations.SUM,
primaryAxisGroupByFieldMetadataId: 'field-x',
secondaryAxisGroupByFieldMetadataId: 'field-y',
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
};
it('should order keys correctly despite unordered raw results', () => {
// This test demonstrates the key ordering issue described in the user query
// Raw results where "CUSTOMER" appears before "NEW" in the data stream despite orderBy,
// bc there is no "NEW" group before october 21st and the results are primarily ordered by date ASC
// but we want them ordered alphabetically-reversed
const rawResults: GroupByRawResult[] = [
{
groupByDimensionValues: ['2025-10-16T00:00:00.000Z', 'SCREENING'],
sumAmount: 75000000000,
},
{
groupByDimensionValues: ['2025-10-16T00:00:00.000Z', 'PROPOSAL'],
sumAmount: 380000000000,
},
{
groupByDimensionValues: ['2025-10-17T00:00:00.000Z', 'CUSTOMER'],
sumAmount: 720000000000,
},
{
groupByDimensionValues: ['2025-10-21T00:00:00.000Z', 'PROPOSAL'],
sumAmount: 580000000000,
},
{
groupByDimensionValues: ['2025-10-21T00:00:00.000Z', 'NEW'],
sumAmount: 125000000000,
},
];
const result = transformTwoDimensionalGroupByToBarChartData({
rawResults,
groupByFieldX: mockGroupByFieldX,
groupByFieldY: mockGroupByFieldY,
aggregateField: mockAggregateField,
configuration: mockConfiguration,
aggregateOperation: 'sumAmount',
objectMetadataItem: mockObjectMetadataItem,
});
expect(result.keys).toEqual(['SCREENING', 'PROPOSAL', 'NEW', 'CUSTOMER']);
expect(result.series).toEqual([
{ key: 'SCREENING', label: 'SCREENING' },
{ key: 'PROPOSAL', label: 'PROPOSAL' },
{ key: 'NEW', label: 'NEW' },
{ key: 'CUSTOMER', label: 'CUSTOMER' },
]);
expect(result.data).toHaveLength(3);
expect(result.data[0]).toEqual({
createdAt: 'Oct 16, 2025',
SCREENING: 75000000000,
PROPOSAL: 380000000000,
});
expect(result.data[1]).toEqual({
createdAt: 'Oct 17, 2025',
CUSTOMER: 720000000000,
});
expect(result.data[2]).toEqual({
createdAt: 'Oct 21, 2025',
PROPOSAL: 580000000000,
NEW: 125000000000,
});
});
});
@@ -1,29 +1,23 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
import { type BarChartConfiguration, GraphOrderBy } from '~/generated/graphql';
import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy';
import {
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForScalarField,
type ObjectRecordOrderByWithGroupByDateField,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type BarChartConfiguration } from '~/generated/graphql';
import { buildGroupByFieldObject } from './buildGroupByFieldObject';
const _mapOrderByToDirection = (orderByEnum: GraphOrderBy) => {
switch (orderByEnum) {
case GraphOrderBy.FIELD_ASC:
return 'AscNullsLast';
case GraphOrderBy.FIELD_DESC:
return 'DescNullsLast';
case GraphOrderBy.VALUE_ASC:
return 'AscNullsLast';
case GraphOrderBy.VALUE_DESC:
return 'DescNullsLast';
default:
assertUnreachable(orderByEnum);
}
};
export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
objectMetadataItem,
barChartConfiguration,
aggregateOperation,
}: {
objectMetadataItem: ObjectMetadataItem;
barChartConfiguration: BarChartConfiguration;
aggregateOperation?: string;
}) => {
const groupByFieldXId =
barChartConfiguration.primaryAxisGroupByFieldMetadataId;
@@ -71,31 +65,41 @@ export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
);
}
const orderBy: Array<Record<string, string>> = [];
const orderBy: Array<
| AggregateOrderByWithGroupByField
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByWithGroupByDateField
| ObjectRecordOrderByForCompositeField
> = [];
// TODO: Add orderBy back in when the backend is ready
// if (isDefined(barChartConfiguration.primaryAxisOrderBy)) {
// orderBy.push({
// [groupByFieldX.name]: mapOrderByToDirection(
// barChartConfiguration.primaryAxisOrderBy!,
// ),
// });
// }
// if (
// isDefined(groupByFieldY) &&
// isDefined(barChartConfiguration.secondaryAxisOrderBy)
// ) {
// orderBy.push({
// [groupByFieldY.name]: mapOrderByToDirection(
// barChartConfiguration.secondaryAxisOrderBy!,
// ),
// });
// }
if (isDefined(barChartConfiguration.primaryAxisOrderBy)) {
orderBy.push(
getGroupByOrderBy({
graphOrderBy: barChartConfiguration.primaryAxisOrderBy,
groupByField: groupByFieldX,
groupBySubFieldName:
barChartConfiguration.primaryAxisGroupBySubFieldName,
aggregateOperation,
}),
);
}
if (
isDefined(groupByFieldY) &&
isDefined(barChartConfiguration.secondaryAxisOrderBy)
) {
orderBy.push(
getGroupByOrderBy({
graphOrderBy: barChartConfiguration.secondaryAxisOrderBy,
groupByField: groupByFieldY,
groupBySubFieldName:
barChartConfiguration.secondaryAxisGroupBySubFieldName,
aggregateOperation,
}),
);
}
return {
groupBy,
// TODO: Add filters
...(orderBy.length > 0 && { orderBy }),
};
};
@@ -0,0 +1,92 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
import {
OrderByDirection,
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForScalarField,
type ObjectRecordOrderByWithGroupByDateField,
} from 'twenty-shared/types';
import {
assertUnreachable,
isDefined,
isFieldMetadataDateKind,
} from 'twenty-shared/utils';
import { GraphOrderBy } from '~/generated/graphql';
const mapOrderByToDirection = (orderByEnum: GraphOrderBy): OrderByDirection => {
switch (orderByEnum) {
case GraphOrderBy.FIELD_ASC:
return OrderByDirection.AscNullsLast;
case GraphOrderBy.FIELD_DESC:
return OrderByDirection.DescNullsLast;
case GraphOrderBy.VALUE_ASC:
return OrderByDirection.AscNullsLast;
case GraphOrderBy.VALUE_DESC:
return OrderByDirection.DescNullsLast;
default:
assertUnreachable(orderByEnum);
}
};
export const getGroupByOrderBy = ({
graphOrderBy,
groupByField,
groupBySubFieldName,
aggregateOperation,
}: {
graphOrderBy: GraphOrderBy;
groupByField: FieldMetadataItem;
groupBySubFieldName?: string | null;
aggregateOperation?: string;
}):
| AggregateOrderByWithGroupByField
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByWithGroupByDateField
| ObjectRecordOrderByForCompositeField => {
switch (graphOrderBy) {
case GraphOrderBy.FIELD_ASC:
case GraphOrderBy.FIELD_DESC: {
if (isCompositeFieldType(groupByField.type)) {
if (!isDefined(groupBySubFieldName)) {
throw new Error(
`Group by subFieldName is required for composite fields (field: ${groupByField.name})`,
);
}
return {
[groupByField.name]: {
[groupBySubFieldName]: mapOrderByToDirection(graphOrderBy),
},
};
} else if (isFieldMetadataDateKind(groupByField.type)) {
return {
[groupByField.name]: {
orderBy: mapOrderByToDirection(graphOrderBy),
granularity: GRAPH_DEFAULT_DATE_GRANULARITY,
},
};
} else {
return {
[groupByField.name]: mapOrderByToDirection(graphOrderBy),
};
}
}
case GraphOrderBy.VALUE_ASC:
case GraphOrderBy.VALUE_DESC: {
if (!isDefined(aggregateOperation)) {
throw new Error(
`Aggregate operation not found (field: ${groupByField.name})`,
);
}
return {
aggregate: {
[aggregateOperation]: mapOrderByToDirection(graphOrderBy),
},
};
}
default:
assertUnreachable(graphOrderBy);
}
};
@@ -0,0 +1,18 @@
import { GraphOrderBy } from '~/generated/graphql';
export const getSortedKeys = ({
orderByY,
yValues,
}: {
orderByY?: GraphOrderBy | null;
yValues: string[];
}) => {
switch (orderByY) {
case GraphOrderBy.FIELD_ASC:
return Array.from(yValues).sort((a, b) => a.localeCompare(b));
case GraphOrderBy.FIELD_DESC:
return Array.from(yValues).sort((a, b) => b.localeCompare(a));
default:
return Array.from(yValues);
}
};
@@ -8,6 +8,7 @@ import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupBy
import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult';
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey';
import { getSortedKeys } from '@/page-layout/widgets/graph/utils/getSortedKeys';
import { isDefined } from 'twenty-shared/utils';
import { type BarChartConfiguration } from '~/generated/graphql';
@@ -101,7 +102,12 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
dataItem[yValue] = aggregateValue;
});
const keys = Array.from(yValues);
// Sorting needed because yValues may be unordered despite BE orderBy, if there are empty groups
const keys = getSortedKeys({
orderByY: configuration.secondaryAxisOrderBy,
yValues: Array.from(yValues),
});
const series: BarChartSeries[] = keys.map((key) => ({
key,
label: key,
@@ -1,9 +1,9 @@
import { Inject, Injectable } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { CommonSelectedFieldsHandler } from 'src/engine/api/common/common-args-handlers/common-query-selected-fields/common-selected-fields.handler';
import { CommonResultGettersService } from 'src/engine/api/common/common-result-getters/common-result-getters.service';
@@ -2,14 +2,13 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord, OrderByDirection } from 'twenty-shared/types';
import { FindOptionsRelations, ObjectLiteral } from 'typeorm';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import {
ObjectRecord,
ObjectRecordFilter,
ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
@@ -1,14 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FindOptionsRelations, ObjectLiteral } from 'typeorm';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import {
ObjectRecord,
ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service';
import {
@@ -1,9 +1,12 @@
import { Injectable, Logger } from '@nestjs/common';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import {
FieldMetadataType,
ObjectRecord,
RelationType,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
import { type QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
@@ -1,7 +1,6 @@
import {
type ObjectRecord,
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type CommonPageInfo } from 'src/engine/api/common/types/common-page-info.type';
import {
@@ -1,4 +1,4 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
export const mockPersonRecords: Partial<ObjectRecord>[] = [
{
@@ -1,19 +1,17 @@
import { isObject } from 'class-validator';
import {
type AggregateOrderByWithGroupByField,
FieldMetadataType,
ObjectRecordGroupByDateGranularity,
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
OrderByDirection,
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderBy,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForScalarField,
type ObjectRecordOrderByWithGroupByDateField,
OrderByDirection,
type OrderByWithGroupBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
GraphqlQueryRunnerException,
@@ -28,8 +26,8 @@ import {
import { getGroupByExpression } from 'src/engine/api/graphql/graphql-query-runner/group-by/resolvers/utils/get-group-by-expression.util';
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
import {
getAvailableAggregationsFromObjectFields,
type AggregationField,
getAvailableAggregationsFromObjectFields,
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@@ -1,4 +1,4 @@
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import {
GraphqlQueryRunnerException,
@@ -1,7 +1,6 @@
import { OrderByDirection } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type OrderByCondition } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
import { convertOrderByToFindOptionsOrder } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/utils/convert-order-by-to-find-options-order';
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
@@ -1,9 +1,9 @@
import { type OrderByWithGroupBy } from 'twenty-shared/types';
import { type FindOptionsWhere, type ObjectLiteral } from 'typeorm';
import {
type ObjectRecordFilter,
type ObjectRecordOrderBy,
type OrderByWithGroupBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import {
CompositeFieldSubFieldName,
ObjectRecord,
PartialFieldMetadataItemOption,
RecordFilterGroupLogicalOperator,
} from 'twenty-shared/types';
@@ -18,10 +19,7 @@ import {
GraphqlQueryBaseResolverService,
GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
ObjectRecord,
ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
import { IGroupByConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/group-by-connection.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
@@ -1,7 +1,9 @@
import { t } from '@lingui/core/macro';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import {
ObjectRecordGroupByDateGranularity,
type ObjectRecord,
} from 'twenty-shared/types';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
import { type IGroupByConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/group-by-connection.interface';
@@ -1,10 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type ObjectRecord,
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { CONNECTION_MAX_DEPTH } from 'src/engine/api/graphql/graphql-query-runner/constants/connection-max-depth.constant';
@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import {
@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import { type FindOptionsRelations, type ObjectLiteral } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper';
import { type AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@@ -1,6 +1,7 @@
import { Inject, Injectable } from '@nestjs/common';
import graphqlFields from 'graphql-fields';
import { type ObjectRecord } from 'twenty-shared/types';
import {
assertIsDefinedOrThrow,
capitalize,
@@ -8,7 +9,6 @@ import {
} from 'twenty-shared/utils';
import { type ObjectLiteral } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { msg } from '@lingui/core/macro';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import {
In,
@@ -14,7 +15,6 @@ import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,10 +1,11 @@
import { Injectable } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type 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 { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,16 +1,13 @@
import { Injectable } from '@nestjs/common';
import isEmpty from 'lodash.isempty';
import { OrderByDirection, type ObjectRecord } from 'twenty-shared/types';
import { In } from 'typeorm';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
type ObjectRecord,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord, OrderByDirection } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -8,10 +9,8 @@ import {
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
type ObjectRecord,
type ObjectRecordFilter,
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
@@ -2,15 +2,13 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
type ObjectRecord,
type ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -4,7 +4,7 @@ import {
MUTATION_MAX_MERGE_RECORDS,
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { In } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
@@ -13,7 +13,6 @@ import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type MergeManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { type FieldMetadataRelationSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type RestoreManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type RestoreOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,12 +1,12 @@
import { Injectable } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { type ObjectRecord } from 'twenty-shared/types';
import {
GraphqlQueryBaseResolverService,
type GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@@ -1,7 +1,6 @@
import {
type ObjectRecord,
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import {
@@ -1,14 +1,9 @@
import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
//TODO : Refacto-common - Should be moved to common api layer
export interface ObjectRecord {
id: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
}
import {
type ObjectRecord,
type ObjectRecordGroupByDateGranularity,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForScalarField,
} from 'twenty-shared/types';
export type ObjectRecordFilter = Partial<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -35,45 +30,10 @@ export type ObjectRecordGroupByForDateField = Partial<{
};
}>;
export enum OrderByDirection {
AscNullsFirst = 'AscNullsFirst',
AscNullsLast = 'AscNullsLast',
DescNullsFirst = 'DescNullsFirst',
DescNullsLast = 'DescNullsLast',
}
export type ObjectRecordOrderBy = Array<
ObjectRecordOrderByForScalarField | ObjectRecordOrderByForCompositeField
>;
export type AggregateOrderByWithGroupByField = {
aggregate: {
[x: string]: OrderByDirection;
};
};
export type ObjectRecordOrderByWithGroupByDateField = {
[Property in keyof ObjectRecord]?: {
orderBy: OrderByDirection;
granularity: ObjectRecordGroupByDateGranularity;
};
};
export type OrderByWithGroupBy = Array<
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByForCompositeField
| ObjectRecordOrderByWithGroupByDateField
| AggregateOrderByWithGroupByField
>;
export type ObjectRecordOrderByForScalarField = {
[Property in keyof ObjectRecord]?: OrderByDirection;
};
export type ObjectRecordOrderByForCompositeField = {
[Property in keyof ObjectRecord]?: Record<string, OrderByDirection>;
};
export type ObjectRecordCursorLeafScalarValue = string | number | boolean;
export type ObjectRecordCursorLeafCompositeValue = Record<
string,
@@ -1,4 +1,5 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { type IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
@@ -1,4 +1,5 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
export const isQueryResultFieldValueANestedRecordArray = (
@@ -1,4 +1,5 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
export const isQueryResultFieldValueARecordArray = (
@@ -1,4 +1,5 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
export const isQueryResultFieldValueARecord = (
@@ -1,4 +1,5 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
export type QueryResultFieldValue =
@@ -1,4 +1,4 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
export interface QueryResultGetterHandlerInterface {
handle(
@@ -1,9 +1,8 @@
import { Injectable, Logger } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
import { type QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
import { type IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
@@ -1,12 +1,9 @@
import { Injectable } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, ObjectRecord } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import {
type ObjectRecord,
type ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type 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 {
type CreateManyResolverArgs,
@@ -3,9 +3,9 @@ import { DiscoveryService, ModuleRef, createContextId } from '@nestjs/core';
import { Injector } from '@nestjs/core/injector/injector';
import { type Module } from '@nestjs/core/injector/module';
import { type ObjectRecord } from 'twenty-shared/types';
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
import {
type WorkspacePostQueryHookInstance,
@@ -1,11 +1,13 @@
import { type GraphQLFieldResolver } from 'graphql';
import {
type ObjectRecord,
type OrderByWithGroupBy,
} from 'twenty-shared/types';
import {
type ObjectRecordFilter,
type ObjectRecordGroupBy,
type ObjectRecordOrderBy,
type OrderByWithGroupBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
@@ -2,9 +2,9 @@ import { BadRequestException, Injectable } from '@nestjs/common';
import { type Request } from 'express';
import isEmpty from 'lodash.isempty';
import { type ObjectRecord } from 'twenty-shared/types';
import { In } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
type FormatResult,
RestApiBaseHandler,
@@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectRecord } from 'twenty-shared/types';
import {
PageInfo,
RestApiBaseHandler,
@@ -1,8 +1,8 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
@@ -4,16 +4,14 @@ import chunk from 'lodash.chunk';
import isEmpty from 'lodash.isempty';
import {
FieldMetadataType,
ObjectRecord,
type RestrictedFieldsPermissions,
} from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { In, type ObjectLiteral } from 'typeorm';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import {
type ObjectRecord,
type ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
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';
@@ -1,6 +1,6 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import {
objectMetadataMapItemMock,
@@ -1,11 +1,9 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { type Request } from 'express';
import { OrderByDirection } from 'twenty-shared/types';
import {
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { checkFields } from 'src/engine/api/rest/core/query-builder/utils/check-fields.utils';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
@@ -1,4 +1,4 @@
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import { parseOrderByRestRequest } from 'src/engine/api/rest/input-request-parsers/order-by-parser-utils/parse-order-by-rest-request.util';
@@ -1,7 +1,6 @@
import {
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
export const addDefaultOrderById = (orderBy: ObjectRecordOrderBy) => {
const hasIdOrder = orderBy.some((o) => Object.keys(o).includes('id'));
@@ -2,10 +2,9 @@
import { BadRequestException } from '@nestjs/common';
import {
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { DEFAULT_ORDER_DIRECTION } from 'src/engine/api/rest/input-factories/order-by-input.factory';
import { addDefaultOrderById } from 'src/engine/api/rest/input-request-parsers/order-by-parser-utils/add-default-order-by-id.util';
@@ -1,10 +1,7 @@
import { type EachTestingContext } from 'twenty-shared/testing';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, OrderByDirection } from 'twenty-shared/types';
import {
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { buildCursorCompositeFieldWhereCondition } from 'src/engine/api/utils/build-cursor-composite-field-where-condition.utils';
@@ -1,6 +1,4 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { FieldMetadataType, OrderByDirection } from 'twenty-shared/types';
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
@@ -1,8 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type ObjectRecord,
type ObjectRecordCursorLeafCompositeValue,
type ObjectRecordFilter,
type ObjectRecordOrderBy,
@@ -1,7 +1,7 @@
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type ObjectRecord,
type ObjectRecordCursorLeafCompositeValue,
type ObjectRecordCursorLeafScalarValue,
type ObjectRecordOrderBy,
@@ -1,9 +1,7 @@
import isEmpty from 'lodash.isempty';
import { type ObjectRecord } from 'twenty-shared/types';
import {
type ObjectRecord,
type ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { settings } from 'src/engine/constants/settings';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
@@ -1,4 +1,4 @@
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
export const isAscendingOrder = (direction: OrderByDirection): boolean =>
direction === OrderByDirection.AscNullsFirst ||
@@ -1,12 +1,12 @@
import { isDefined } from 'twenty-shared/utils';
import {
type ObjectRecord,
type ObjectRecordOrderBy,
type ObjectRecordOrderByForCompositeField,
type ObjectRecordOrderByForScalarField,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
} from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordOrderBy } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import {
GraphqlQueryRunnerException,
@@ -1,6 +1,5 @@
import deepEqual from 'deep-equal';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
@@ -1,7 +1,5 @@
import deepEqual from 'deep-equal';
import { FieldMetadataType } from 'twenty-shared/types';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
@@ -2,8 +2,7 @@ import {
QUERY_DEFAULT_LIMIT_RECORDS,
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
import {
computeDepthParameters,
@@ -3,8 +3,7 @@ import {
QUERY_DEFAULT_LIMIT_RECORDS,
QUERY_MAX_RECORDS,
} from 'twenty-shared/constants';
import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OrderByDirection } from 'twenty-shared/types';
export const computeLimitParameters = (
fromMetadata = false,
@@ -1,12 +1,12 @@
import { Injectable, Logger } from '@nestjs/common';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { OrderByDirection } from 'twenty-shared/types';
import { type ObjectLiteral } from 'typeorm';
import {
type ObjectRecordFilter,
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
@@ -1,10 +1,9 @@
import { Injectable } from '@nestjs/common';
import { isNumber } from '@sniptt/guards';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
export type RecordPositionServiceCreateArgs = {
@@ -2,14 +2,11 @@ import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import chunk from 'lodash.chunk';
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, ObjectRecord } from 'twenty-shared/types';
import { getLogoUrlFromDomainName } from 'twenty-shared/utils';
import { Brackets, type ObjectLiteral } from 'typeorm';
import {
type ObjectRecord,
type ObjectRecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import {
@@ -1,4 +1,4 @@
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
@@ -1,8 +1,7 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
import GraphQLJSON from 'graphql-type-json';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { ObjectRecord } from 'twenty-shared/types';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@@ -1,10 +1,9 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { type ObjectRecord } from 'twenty-shared/types';
import { In, MoreThan } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { objectRecordDiffMerge } from 'src/engine/core-modules/event-emitter/utils/object-record-diff-merge';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { type TimelineActivityPayload } from 'src/modules/timeline/types/timeline-activity-payload';
@@ -1,18 +1,17 @@
import { Injectable } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { In } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecordBaseEvent } from 'src/engine/core-modules/event-emitter/types/object-record.base.event';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
import { parseEventNameOrThrow } from 'src/engine/workspace-event-emitter/utils/parse-event-name';
import { TimelineActivityRepository } from 'src/modules/timeline/repositories/timeline-activity.repository';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
import { type TimelineActivityPayload } from 'src/modules/timeline/types/timeline-activity-payload';
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
type ActivityType = 'note' | 'task';
@@ -1,10 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { In, Raw } from 'typeorm';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
@@ -13,8 +13,7 @@ import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graph
import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils';
import { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util';
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
@@ -3,10 +3,9 @@ import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graph
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type ObjectRecord } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
type CreateOneOperationArgs<T> = PerformMetadataQueryParams<T> & {
objectMetadataSingularName: string;
};
@@ -2,8 +2,7 @@ import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type ObjectRecord } from 'twenty-shared/types';
type FindOneOperationArgs = Parameters<typeof findOneOperationFactory>[0] & {
expectToFail?: boolean;
@@ -1,11 +1,10 @@
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type';
import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type';
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
import { type ObjectRecord } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
import { type ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
type UpdateOneOperationArgs<T> = PerformMetadataQueryParams<T> & {
objectMetadataSingularName: string;
@@ -0,0 +1,8 @@
export interface ObjectRecord {
id: string;
[key: string]: any;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
}
@@ -0,0 +1,37 @@
import { type ObjectRecord } from '@/types/ObjectRecord';
import { type ObjectRecordGroupByDateGranularity } from '@/types/ObjectRecordGroupByDateGranularity';
export enum OrderByDirection {
AscNullsFirst = 'AscNullsFirst',
AscNullsLast = 'AscNullsLast',
DescNullsFirst = 'DescNullsFirst',
DescNullsLast = 'DescNullsLast',
}
export type AggregateOrderByWithGroupByField = {
aggregate: {
[x: string]: OrderByDirection;
};
};
export type ObjectRecordOrderByWithGroupByDateField = {
[Property in keyof ObjectRecord]?: {
orderBy: OrderByDirection;
granularity: ObjectRecordGroupByDateGranularity;
};
};
export type OrderByWithGroupBy = Array<
| ObjectRecordOrderByForScalarField
| ObjectRecordOrderByForCompositeField
| ObjectRecordOrderByWithGroupByDateField
| AggregateOrderByWithGroupByField
>;
export type ObjectRecordOrderByForScalarField = {
[Property in keyof ObjectRecord]?: OrderByDirection;
};
export type ObjectRecordOrderByForCompositeField = {
[Property in keyof ObjectRecord]?: Record<string, OrderByDirection>;
};
@@ -34,6 +34,15 @@ export type { Nullable } from './Nullable';
export type { NullablePartial } from './NullablePartial';
export type { ObjectPermissions } from './ObjectPermissions';
export type { ObjectPermissionsDeprecated } from './ObjectPermissionsDeprecated';
export type { ObjectRecord } from './ObjectRecord';
export type {
AggregateOrderByWithGroupByField,
ObjectRecordOrderByWithGroupByDateField,
OrderByWithGroupBy,
ObjectRecordOrderByForScalarField,
ObjectRecordOrderByForCompositeField,
} from './ObjectRecordGroupBy';
export { OrderByDirection } from './ObjectRecordGroupBy';
export { ObjectRecordGroupByDateGranularity } from './ObjectRecordGroupByDateGranularity';
export type { ObjectsPermissions } from './ObjectsPermissions';
export type { ObjectsPermissionsByRoleId } from './ObjectsPermissionsByRoleId';