unlock relation date fields on dashboards (#16207)

This commit is contained in:
nitin
2025-12-01 22:18:52 +05:30
committed by GitHub
parent 32f387a966
commit a8e7d4dfc3
22 changed files with 818 additions and 25 deletions
@@ -42,6 +42,26 @@ describe('buildGroupByFieldObject', () => {
});
});
it('should return date granularity for relation date field', () => {
const field = {
name: 'company',
type: FieldMetadataType.RELATION,
} as any;
const result = buildGroupByFieldObject({
field,
subFieldName: 'createdAt',
dateGranularity: ObjectRecordGroupByDateGranularity.MONTH,
isNestedDateField: true,
});
expect(result).toEqual({
company: {
createdAt: { granularity: ObjectRecordGroupByDateGranularity.MONTH },
},
});
});
it('should return nested object for composite fields with subfield', () => {
const field = {
name: 'name',
@@ -0,0 +1,58 @@
import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues';
import {
FieldMetadataType,
ObjectRecordGroupByDateGranularity,
} from 'twenty-shared/types';
jest.mock('@/page-layout/widgets/graph/utils/formatDimensionValue', () => ({
formatDimensionValue: jest.fn((args: { value: unknown }) => {
return `formatted-${String(args.value)}`;
}),
}));
const { formatDimensionValue } = jest.requireMock(
'@/page-layout/widgets/graph/utils/formatDimensionValue',
) as { formatDimensionValue: jest.Mock };
describe('formatPrimaryDimensionValues', () => {
it('includes buckets where the primary dimension value is null', () => {
const result = formatPrimaryDimensionValues({
groupByRawResults: [
{ groupByDimensionValues: [null] },
{ groupByDimensionValues: ['Active'] },
],
primaryAxisGroupByField: {
name: 'status',
type: FieldMetadataType.TEXT,
} as any,
});
expect(result).toHaveLength(2);
expect(result[0].rawPrimaryDimensionValue).toBeNull();
expect(result[0].formattedPrimaryDimensionValue).toBe('formatted-null');
expect(result[1].formattedPrimaryDimensionValue).toBe('formatted-Active');
});
it('passes granularity and subfield to the formatter', () => {
formatDimensionValue.mockClear();
formatPrimaryDimensionValues({
groupByRawResults: [
{ groupByDimensionValues: ['2024-01-15T00:00:00.000Z'] },
],
primaryAxisGroupByField: {
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
} as any,
primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.MONTH,
primaryAxisGroupBySubFieldName: 'createdAt',
});
expect(formatDimensionValue).toHaveBeenCalledWith({
value: '2024-01-15T00:00:00.000Z',
fieldMetadata: expect.objectContaining({ name: 'createdAt' }),
dateGranularity: ObjectRecordGroupByDateGranularity.MONTH,
subFieldName: 'createdAt',
});
});
});
@@ -1,6 +1,9 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateGroupByQueryVariablesFromBarOrLineChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration';
import { FieldMetadataType } from 'twenty-shared/types';
import {
FieldMetadataType,
ObjectRecordGroupByDateGranularity,
} from 'twenty-shared/types';
import {
AggregateOperations,
type BarChartConfiguration,
@@ -67,6 +70,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -81,6 +85,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -97,6 +102,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-4',
@@ -111,6 +117,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-3',
@@ -128,6 +135,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.HORIZONTAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -142,6 +150,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.HORIZONTAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -152,6 +161,98 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
expect(result).toMatchSnapshot();
});
});
it('applies date granularity when primary axis uses a relation date subfield', () => {
const relationField = {
id: 'field-rel',
name: 'company',
type: FieldMetadataType.RELATION,
relation: { targetObjectMetadata: { nameSingular: 'company' } },
};
const objectMetadataItem: ObjectMetadataItem = {
id: 'obj-main',
nameSingular: 'opportunity',
namePlural: 'opportunities',
fields: [relationField],
} as ObjectMetadataItem;
const targetObjectMetadata: ObjectMetadataItem = {
id: 'obj-company',
nameSingular: 'company',
namePlural: 'companies',
fields: [
{
id: 'company-created-at',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
},
],
} as ObjectMetadataItem;
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem,
objectMetadataItems: [objectMetadataItem, targetObjectMetadata],
chartConfiguration: buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: relationField.id,
primaryAxisGroupBySubFieldName: 'createdAt',
}),
});
expect(result.groupBy[0]).toEqual({
company: {
createdAt: { granularity: ObjectRecordGroupByDateGranularity.DAY },
},
});
});
it('applies date granularity for relation date subfield in line charts as well', () => {
const relationField = {
id: 'field-rel',
name: 'company',
type: FieldMetadataType.RELATION,
relation: { targetObjectMetadata: { nameSingular: 'company' } },
};
const objectMetadataItem: ObjectMetadataItem = {
id: 'obj-main',
nameSingular: 'opportunity',
namePlural: 'opportunities',
fields: [relationField],
} as ObjectMetadataItem;
const targetObjectMetadata: ObjectMetadataItem = {
id: 'obj-company',
nameSingular: 'company',
namePlural: 'companies',
fields: [
{
id: 'company-created-at',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
},
],
} as ObjectMetadataItem;
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem,
objectMetadataItems: [objectMetadataItem, targetObjectMetadata],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: relationField.id,
primaryAxisGroupBySubFieldName: 'createdAt',
}),
});
expect(result.groupBy[0]).toEqual({
company: {
createdAt: { granularity: ObjectRecordGroupByDateGranularity.DAY },
},
});
});
});
describe('Line Chart Configuration', () => {
@@ -159,6 +260,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -173,6 +275,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -189,6 +292,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: 'field-3',
@@ -204,6 +308,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: 'field-4',
@@ -218,6 +323,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
const result =
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
graphType: GraphType.LINE,
primaryAxisGroupByFieldMetadataId: 'field-1',
@@ -234,6 +340,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
expect(() =>
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildBarChartConfiguration({
primaryAxisGroupByFieldMetadataId: 'invalid-field',
}),
@@ -245,6 +352,7 @@ describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => {
expect(() =>
generateGroupByQueryVariablesFromBarOrLineChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildLineChartConfiguration({
primaryAxisGroupByFieldMetadataId: 'invalid-field',
}),
@@ -6,6 +6,7 @@ import {
GraphType,
type PieChartConfiguration,
} from '~/generated-metadata/graphql';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { generateGroupByQueryVariablesFromPieChartConfiguration } from '../generateGroupByQueryVariablesFromPieChartConfiguration';
describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
@@ -53,6 +54,7 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
it('should generate variables with single groupBy field', () => {
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'field-1',
groupBySubFieldName: null,
@@ -65,6 +67,7 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
it('should generate variables with composite field', () => {
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'field-4',
groupBySubFieldName: 'firstName',
@@ -77,6 +80,7 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
it('should generate variables with date field and granularity', () => {
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'field-3',
groupBySubFieldName: null,
@@ -90,6 +94,7 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
it('should generate variables with limit', () => {
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'field-1',
}),
@@ -103,6 +108,7 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
it('should generate variables with orderBy', () => {
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'field-1',
orderBy: GraphOrderBy.VALUE_ASC,
@@ -115,11 +121,58 @@ describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => {
});
});
describe('Relation date subfield', () => {
it('applies date granularity when grouping by a relation date subfield', () => {
const relationField = {
id: 'field-rel',
name: 'company',
type: FieldMetadataType.RELATION,
relation: { targetObjectMetadata: { nameSingular: 'company' } },
};
const mainObjectMetadata: ObjectMetadataItem = {
id: 'obj-main',
nameSingular: 'opportunity',
namePlural: 'opportunities',
fields: [relationField],
} as ObjectMetadataItem;
const targetObjectMetadata: ObjectMetadataItem = {
id: 'obj-company',
nameSingular: 'company',
namePlural: 'companies',
fields: [
{
id: 'company-created-at',
name: 'createdAt',
type: FieldMetadataType.DATE_TIME,
},
],
} as ObjectMetadataItem;
const result = generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mainObjectMetadata,
objectMetadataItems: [mainObjectMetadata, targetObjectMetadata],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: relationField.id,
groupBySubFieldName: 'createdAt',
}),
});
expect(result.groupBy[0]).toEqual({
company: {
createdAt: { granularity: ObjectRecordGroupByDateGranularity.DAY },
},
});
});
});
describe('Error Handling', () => {
it('should throw error when groupBy field not found', () => {
expect(() =>
generateGroupByQueryVariablesFromPieChartConfiguration({
objectMetadataItem: mockObjectMetadataItem,
objectMetadataItems: [],
chartConfiguration: buildPieChartConfiguration({
groupByFieldMetadataId: 'invalid-field',
}),
@@ -0,0 +1,58 @@
import { getRelationFieldOrderBy } from '@/page-layout/widgets/graph/utils/getRelationFieldOrderBy';
import {
FieldMetadataType,
ObjectRecordGroupByDateGranularity,
OrderByDirection,
} from 'twenty-shared/types';
describe('getRelationFieldOrderBy', () => {
const relationField = {
name: 'company',
type: FieldMetadataType.RELATION,
} as any;
it('returns relation id ordering when no subfield provided', () => {
const orderBy = getRelationFieldOrderBy(
relationField,
null,
OrderByDirection.AscNullsLast,
);
expect(orderBy).toEqual({ companyId: OrderByDirection.AscNullsLast });
});
it('adds granularity when nested relation field is a date field', () => {
const orderBy = getRelationFieldOrderBy(
relationField,
'createdAt',
OrderByDirection.DescNullsLast,
undefined,
true,
);
expect(orderBy).toEqual({
company: {
createdAt: {
orderBy: OrderByDirection.DescNullsLast,
granularity: ObjectRecordGroupByDateGranularity.DAY,
},
},
});
});
it('returns nested relation ordering without granularity for non-date subfield', () => {
const orderBy = getRelationFieldOrderBy(
relationField,
'name',
OrderByDirection.AscNullsLast,
undefined,
false,
);
expect(orderBy).toEqual({
company: {
name: OrderByDirection.AscNullsLast,
},
});
});
});
@@ -0,0 +1,57 @@
import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType';
import { FieldMetadataType } from 'twenty-shared/types';
describe('isNestedFieldDateType', () => {
const companyObject = {
id: 'company-id',
nameSingular: 'company',
namePlural: 'companies',
fields: [
{ id: 'createdAt', name: 'createdAt', type: FieldMetadataType.DATE_TIME },
{ id: 'name', name: 'name', type: FieldMetadataType.TEXT },
],
} as any;
const relationField = {
name: 'company',
type: FieldMetadataType.RELATION,
relation: { targetObjectMetadata: { nameSingular: 'company' } },
} as any;
it('returns true for a relation subfield that is a date type', () => {
const result = isNestedFieldDateType(relationField, 'createdAt', [
companyObject,
]);
expect(result).toBe(true);
});
it('returns false when the nested subfield is not a date type', () => {
const result = isNestedFieldDateType(relationField, 'name', [
companyObject,
]);
expect(result).toBe(false);
});
it('returns false when subFieldName is missing', () => {
const result = isNestedFieldDateType(relationField, undefined, [
companyObject,
]);
expect(result).toBe(false);
});
it('returns false for non-relation fields', () => {
const nonRelationField = {
name: 'status',
type: FieldMetadataType.TEXT,
} as any;
const result = isNestedFieldDateType(nonRelationField, 'createdAt', [
companyObject,
]);
expect(result).toBe(false);
});
});
@@ -20,11 +20,13 @@ export const buildGroupByFieldObject = ({
subFieldName,
dateGranularity,
firstDayOfTheWeek,
isNestedDateField,
}: {
field: FieldMetadataItem;
subFieldName?: string | null;
dateGranularity?: ObjectRecordGroupByDateGranularity;
firstDayOfTheWeek?: number | null;
isNestedDateField?: boolean;
}): GroupByFieldObject => {
const isRelation = isFieldRelation(field) || isFieldMorphRelation(field);
const isComposite = isCompositeFieldType(field.type);
@@ -39,6 +41,16 @@ export const buildGroupByFieldObject = ({
const nestedFieldName = parts[0];
const nestedSubFieldName = parts[1];
if (isNestedDateField === true || isDefined(dateGranularity)) {
return {
[field.name]: {
[nestedFieldName]: {
granularity: dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY,
},
},
};
}
if (isDefined(nestedSubFieldName)) {
return {
[field.name]: {
@@ -88,6 +88,26 @@ export const formatDimensionValue = ({
return formatDateByGranularity(new Date(String(value)), dateGranularity);
}
case FieldMetadataType.RELATION: {
if (isDefined(dateGranularity)) {
if (
dateGranularity ===
ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK ||
dateGranularity ===
ObjectRecordGroupByDateGranularity.MONTH_OF_THE_YEAR ||
dateGranularity ===
ObjectRecordGroupByDateGranularity.QUARTER_OF_THE_YEAR
) {
return String(value);
}
return formatDateByGranularity(
new Date(String(value)),
dateGranularity,
);
}
return String(value);
}
case FieldMetadataType.NUMBER:
case FieldMetadataType.CURRENCY: {
if (
@@ -2,7 +2,6 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
import { isDefined } from 'twenty-shared/utils';
import { type ObjectRecordGroupByDateGranularity } from '~/generated/graphql';
type FormatPrimaryDimensionValuesParameters = {
@@ -27,12 +26,8 @@ export const formatPrimaryDimensionValues = ({
(accumulator, rawResult) => {
const groupByDimensionValues = rawResult.groupByDimensionValues;
if (!isDefined(groupByDimensionValues?.[0])) {
return accumulator;
}
const rawPrimaryDimensionValue =
groupByDimensionValues[0] as RawDimensionValue;
const rawPrimaryDimensionValue = (groupByDimensionValues?.[0] ??
null) as RawDimensionValue;
const formattedPrimaryDimensionValue = formatDimensionValue({
value: rawPrimaryDimensionValue,
@@ -1,6 +1,7 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy';
import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType';
import {
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderByForCompositeField,
@@ -20,12 +21,14 @@ import {
export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
objectMetadataItem,
objectMetadataItems,
chartConfiguration,
aggregateOperation,
limit,
firstDayOfTheWeek,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
chartConfiguration: BarChartConfiguration | LineChartConfiguration;
aggregateOperation?: string;
limit?: number;
@@ -58,33 +61,50 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
const isFieldXDate = isFieldMetadataDateKind(groupByFieldX.type);
const isFieldXNestedDate = isNestedFieldDateType(
groupByFieldX,
groupBySubFieldNameX,
objectMetadataItems,
);
const shouldApplyDateGranularityX = isFieldXDate || isFieldXNestedDate;
const groupBy: Array<GroupByFieldObject> = [];
groupBy.push(
buildGroupByFieldObject({
field: groupByFieldX,
subFieldName: groupBySubFieldNameX,
dateGranularity: isFieldXDate
dateGranularity: shouldApplyDateGranularityX
? (chartConfiguration.primaryAxisDateGranularity ??
GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
firstDayOfTheWeek,
isNestedDateField: isFieldXNestedDate,
}),
);
if (isDefined(groupByFieldY)) {
const isFieldYDate = isFieldMetadataDateKind(groupByFieldY.type);
const isFieldYNestedDate = isNestedFieldDateType(
groupByFieldY,
groupBySubFieldNameY,
objectMetadataItems,
);
const shouldApplyDateGranularityY = isFieldYDate || isFieldYNestedDate;
groupBy.push(
buildGroupByFieldObject({
field: groupByFieldY,
subFieldName: groupBySubFieldNameY,
dateGranularity: isFieldYDate
dateGranularity: shouldApplyDateGranularityY
? (chartConfiguration.secondaryAxisGroupByDateGranularity ??
GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
firstDayOfTheWeek,
isNestedDateField: isFieldYNestedDate,
}),
);
}
@@ -104,7 +124,7 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
groupByField: groupByFieldX,
groupBySubFieldName: chartConfiguration.primaryAxisGroupBySubFieldName,
aggregateOperation,
dateGranularity: isFieldXDate
dateGranularity: shouldApplyDateGranularityX
? (chartConfiguration.primaryAxisDateGranularity ??
GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
@@ -117,6 +137,15 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
) {
const isFieldYDateForOrderBy = isFieldMetadataDateKind(groupByFieldY.type);
const isFieldYNestedDateForOrderBy = isNestedFieldDateType(
groupByFieldY,
groupBySubFieldNameY,
objectMetadataItems,
);
const shouldApplyDateGranularityYForOrderBy =
isFieldYDateForOrderBy || isFieldYNestedDateForOrderBy;
orderBy.push(
getGroupByOrderBy({
graphOrderBy: chartConfiguration.secondaryAxisOrderBy,
@@ -124,7 +153,7 @@ export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({
groupBySubFieldName:
chartConfiguration.secondaryAxisGroupBySubFieldName,
aggregateOperation,
dateGranularity: isFieldYDateForOrderBy
dateGranularity: shouldApplyDateGranularityYForOrderBy
? (chartConfiguration.secondaryAxisGroupByDateGranularity ??
GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
@@ -1,6 +1,7 @@
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy';
import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType';
import {
type AggregateOrderByWithGroupByField,
type ObjectRecordOrderByForCompositeField,
@@ -17,12 +18,14 @@ import {
export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
objectMetadataItem,
objectMetadataItems,
chartConfiguration,
aggregateOperation,
limit,
firstDayOfTheWeek,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
chartConfiguration: PieChartConfiguration;
aggregateOperation?: string;
limit?: number;
@@ -45,15 +48,23 @@ export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
const isFieldDate = isFieldMetadataDateKind(groupByField.type);
const isNestedDate = isNestedFieldDateType(
groupByField,
groupBySubFieldName,
objectMetadataItems,
);
const shouldApplyDateGranularity = isFieldDate || isNestedDate;
const groupBy: Array<GroupByFieldObject> = [
buildGroupByFieldObject({
field: groupByField,
subFieldName: groupBySubFieldName,
dateGranularity: isFieldDate
dateGranularity: shouldApplyDateGranularity
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
firstDayOfTheWeek,
isNestedDateField: isNestedDate,
}),
];
@@ -72,7 +83,7 @@ export const generateGroupByQueryVariablesFromPieChartConfiguration = ({
groupByField,
groupBySubFieldName,
aggregateOperation,
dateGranularity: isFieldDate
dateGranularity: shouldApplyDateGranularity
? (dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY)
: undefined,
}),
@@ -51,6 +51,7 @@ export const getFieldOrderBy = (
groupByField,
groupBySubFieldName,
direction,
dateGranularity,
);
}
@@ -1,5 +1,7 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { GRAPH_DEFAULT_DATE_GRANULARITY } from '@/page-layout/widgets/graph/constants/GraphDefaultDateGranularity.constant';
import {
type ObjectRecordGroupByDateGranularity,
type ObjectRecordOrderByForRelationField,
type ObjectRecordOrderByForScalarField,
type OrderByDirection,
@@ -10,6 +12,8 @@ export const getRelationFieldOrderBy = (
groupByField: FieldMetadataItem,
groupBySubFieldName: string | null | undefined,
direction: OrderByDirection,
dateGranularity?: ObjectRecordGroupByDateGranularity,
isNestedDateField?: boolean,
): ObjectRecordOrderByForScalarField | ObjectRecordOrderByForRelationField => {
if (!isDefined(groupBySubFieldName)) {
return {
@@ -19,6 +23,17 @@ export const getRelationFieldOrderBy = (
const [nestedFieldName, nestedSubFieldName] = groupBySubFieldName.split('.');
if (isNestedDateField === true || isDefined(dateGranularity)) {
return {
[groupByField.name]: {
[nestedFieldName]: {
orderBy: direction,
granularity: dateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY,
},
},
};
}
if (!isDefined(nestedSubFieldName)) {
return {
[groupByField.name]: {
@@ -0,0 +1,44 @@
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
export const isNestedFieldDateType = (
field: FieldMetadataItem,
subFieldName: string | undefined,
objectMetadataItems: ObjectMetadataItem[],
): boolean => {
if (!isDefined(subFieldName)) {
return false;
}
if (!isFieldRelation(field)) {
return false;
}
const targetObjectNameSingular =
field.relation?.targetObjectMetadata?.nameSingular;
if (!isDefined(targetObjectNameSingular)) {
return false;
}
const targetObjectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === targetObjectNameSingular,
);
if (!isDefined(targetObjectMetadataItem)) {
return false;
}
const nestedFieldName = subFieldName.split('.')[0];
const nestedField = targetObjectMetadataItem.fields.find(
(f) => f.name === nestedFieldName,
);
if (!isDefined(nestedField)) {
return false;
}
return isFieldMetadataDateKind(nestedField.type);
};