diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts index e7d126b26d..0fe7ae7e5c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/hooks/useGraphBarChartWidgetData.ts @@ -1,4 +1,5 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout'; import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries'; import { getBarChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit'; @@ -40,6 +41,7 @@ export const useGraphBarChartWidgetData = ({ const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, }); + const { objectMetadataItems } = useObjectMetadataItems(); const limit = getBarChartQueryLimit(configuration); @@ -59,10 +61,17 @@ export const useGraphBarChartWidgetData = ({ transformGroupByDataToBarChartData({ groupByData, objectMetadataItem, + objectMetadataItems: objectMetadataItems ?? [], configuration, aggregateOperation, }), - [groupByData, objectMetadataItem, configuration, aggregateOperation], + [ + groupByData, + objectMetadataItem, + objectMetadataItems, + configuration, + aggregateOperation, + ], ); return { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/transformGroupByDataToBarChartData.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/transformGroupByDataToBarChartData.test.ts index 7071e24811..5684e09fcf 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/transformGroupByDataToBarChartData.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/__tests__/transformGroupByDataToBarChartData.test.ts @@ -44,6 +44,7 @@ describe('transformGroupByDataToBarChartData', () => { id: 'group-by-field', name: 'company', type: FieldMetadataType.RELATION, + relation: { targetObjectMetadata: { nameSingular: 'company' } }, }; const aggregateField = { @@ -56,9 +57,15 @@ describe('transformGroupByDataToBarChartData', () => { id: 'obj-1', nameSingular: 'company', namePlural: 'companies', - fields: [groupByField, aggregateField], + fields: [ + groupByField, + aggregateField, + { id: 'createdAt', name: 'createdAt', type: FieldMetadataType.DATE }, + ], } as any; + const objectMetadataItems = [objectMetadataItem]; + const configuration = { __typename: 'BarChartConfiguration', aggregateFieldMetadataId: aggregateField.id, @@ -81,6 +88,7 @@ describe('transformGroupByDataToBarChartData', () => { const result = transformGroupByDataToBarChartData({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation: 'COUNT', }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts index 965151db25..0f145454af 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData.ts @@ -13,6 +13,7 @@ import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupBy import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue'; import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults'; import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey'; +import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType'; import { type BarDatum } from '@nivo/bar'; import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; import { GraphType } from '~/generated-metadata/graphql'; @@ -24,6 +25,7 @@ import { type TransformGroupByDataToBarChartDataParams = { groupByData: Record | null | undefined; objectMetadataItem: ObjectMetadataItem; + objectMetadataItems: ObjectMetadataItem[]; configuration: BarChartConfiguration; aggregateOperation: string; }; @@ -59,6 +61,7 @@ const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = { export const transformGroupByDataToBarChartData = ({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation, }: TransformGroupByDataToBarChartDataParams): TransformGroupByDataToBarChartDataResult => { @@ -97,6 +100,8 @@ export const transformGroupByDataToBarChartData = ({ const primaryAxisSubFieldName = configuration.primaryAxisGroupBySubFieldName ?? undefined; + const secondaryAxisSubFieldName = + configuration.secondaryAxisGroupBySubFieldName ?? undefined; const indexByKey = getFieldKey({ field: groupByFieldX, @@ -154,9 +159,44 @@ export const transformGroupByDataToBarChartData = ({ const showLegend = configuration.displayLegend ?? true; const isDateField = isFieldMetadataDateKind(groupByFieldX.type); - const isNestedDateField = - !isDateField && isDefined(configuration.primaryAxisDateGranularity); - const shouldApplyDateGapFill = isDateField || isNestedDateField; + const isNestedDateField = isNestedFieldDateType( + groupByFieldX, + primaryAxisSubFieldName, + objectMetadataItems, + ); + + const primaryAxisDateGranularity = + isDateField || isNestedDateField + ? (configuration.primaryAxisDateGranularity ?? + GRAPH_DEFAULT_DATE_GRANULARITY) + : undefined; + + const isSecondaryDateField = isDefined(groupByFieldY) + ? isFieldMetadataDateKind(groupByFieldY.type) + : false; + + const isSecondaryNestedDateField = + isDefined(groupByFieldY) && + isNestedFieldDateType( + groupByFieldY, + secondaryAxisSubFieldName ?? undefined, + objectMetadataItems, + ); + + const secondaryAxisDateGranularity = + isSecondaryDateField || isSecondaryNestedDateField + ? (configuration.secondaryAxisGroupByDateGranularity ?? + GRAPH_DEFAULT_DATE_GRANULARITY) + : undefined; + + const sanitizedConfiguration: BarChartConfiguration = { + ...configuration, + primaryAxisDateGranularity: primaryAxisDateGranularity ?? undefined, + secondaryAxisGroupByDateGranularity: + secondaryAxisDateGranularity ?? undefined, + }; + + const shouldApplyDateGapFill = isDefined(primaryAxisDateGranularity); const omitNullValues = configuration.omitNullValues ?? false; @@ -166,8 +206,7 @@ export const transformGroupByDataToBarChartData = ({ data: filteredResults, keys: [aggregateField.name], dateGranularity: - configuration.primaryAxisDateGranularity ?? - GRAPH_DEFAULT_DATE_GRANULARITY, + primaryAxisDateGranularity ?? GRAPH_DEFAULT_DATE_GRANULARITY, hasSecondDimension: isDefined(groupByFieldY), }) : { data: filteredResults, wasTruncated: false }; @@ -181,7 +220,7 @@ export const transformGroupByDataToBarChartData = ({ groupByFieldX, groupByFieldY, aggregateField, - configuration, + configuration: sanitizedConfiguration, aggregateOperation, objectMetadataItem, primaryAxisSubFieldName, @@ -190,7 +229,7 @@ export const transformGroupByDataToBarChartData = ({ rawResults: filteredResultsWithDateGaps, groupByFieldX, aggregateField, - configuration, + configuration: sanitizedConfiguration, aggregateOperation, objectMetadataItem, primaryAxisSubFieldName, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts index b35225540d..fcb6c6704e 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetLineChart/hooks/useGraphLineChartWidgetData.ts @@ -1,4 +1,5 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries'; import { getLineChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit'; import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery'; @@ -34,6 +35,7 @@ export const useGraphLineChartWidgetData = ({ const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, }); + const { objectMetadataItems } = useObjectMetadataItems(); const limit = getLineChartQueryLimit(configuration); @@ -53,10 +55,17 @@ export const useGraphLineChartWidgetData = ({ transformGroupByDataToLineChartData({ groupByData, objectMetadataItem, + objectMetadataItems: objectMetadataItems ?? [], configuration, aggregateOperation, }), - [groupByData, objectMetadataItem, configuration, aggregateOperation], + [ + groupByData, + objectMetadataItem, + objectMetadataItems, + configuration, + aggregateOperation, + ], ); return { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts index bceac7a8ba..bf91246099 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts @@ -1,4 +1,5 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant'; import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; @@ -33,6 +34,7 @@ export const useGraphPieChartWidgetData = ({ const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, }); + const { objectMetadataItems } = useObjectMetadataItems(); const { data: groupByData, @@ -51,10 +53,17 @@ export const useGraphPieChartWidgetData = ({ transformGroupByDataToPieChartData({ groupByData, objectMetadataItem, + objectMetadataItems: objectMetadataItems ?? [], configuration, aggregateOperation, }), - [groupByData, objectMetadataItem, configuration, aggregateOperation], + [ + groupByData, + objectMetadataItem, + objectMetadataItems, + configuration, + aggregateOperation, + ], ); return { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/transformGroupByDataToPieChartData.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/transformGroupByDataToPieChartData.test.ts index 006e2c5bec..562f9bf232 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/transformGroupByDataToPieChartData.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/__tests__/transformGroupByDataToPieChartData.test.ts @@ -41,6 +41,7 @@ describe('transformGroupByDataToPieChartData', () => { namePlural: 'companies', fields: [groupByField, aggregateField], } as any; + const objectMetadataItems = [objectMetadataItem]; const configuration = { __typename: 'PieChartConfiguration', @@ -72,6 +73,7 @@ describe('transformGroupByDataToPieChartData', () => { const result = transformGroupByDataToPieChartData({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation: 'COUNT', }); @@ -106,6 +108,7 @@ describe('transformGroupByDataToPieChartData', () => { namePlural: 'companies', fields: [groupByField, aggregateField], } as any; + const objectMetadataItems = [objectMetadataItem]; const configuration = { __typename: 'PieChartConfiguration', @@ -137,6 +140,7 @@ describe('transformGroupByDataToPieChartData', () => { const result = transformGroupByDataToPieChartData({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation: 'COUNT', }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts index 4652326f19..a51a458cf8 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts @@ -11,12 +11,15 @@ import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDim import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup'; import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult'; import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues'; -import { isDefined } from 'twenty-shared/utils'; +import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType'; +import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; +import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; import { type PieChartConfiguration } from '~/generated/graphql'; type TransformGroupByDataToPieChartDataParams = { groupByData: Record | null | undefined; objectMetadataItem: ObjectMetadataItem; + objectMetadataItems: ObjectMetadataItem[]; configuration: PieChartConfiguration; aggregateOperation: string; }; @@ -38,6 +41,7 @@ const EMPTY_PIE_CHART_RESULT: TransformGroupByDataToPieChartDataResult = { export const transformGroupByDataToPieChartData = ({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation, }: TransformGroupByDataToPieChartDataParams): TransformGroupByDataToPieChartDataResult => { @@ -66,6 +70,18 @@ export const transformGroupByDataToPieChartData = ({ return EMPTY_PIE_CHART_RESULT; } + const isDateField = isFieldMetadataDateKind(groupByField.type); + const isNestedDateField = isNestedFieldDateType( + groupByField, + configuration.groupBySubFieldName ?? undefined, + objectMetadataItems, + ); + + const dateGranularity: ObjectRecordGroupByDateGranularity | undefined = + isDateField || isNestedDateField + ? (configuration.dateGranularity ?? undefined) + : undefined; + // TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600) const limitedResults = rawResults.slice( 0, @@ -75,7 +91,7 @@ export const transformGroupByDataToPieChartData = ({ const formattedValues = formatPrimaryDimensionValues({ groupByRawResults: limitedResults, primaryAxisGroupByField: groupByField, - primaryAxisDateGranularity: configuration.dateGranularity ?? undefined, + primaryAxisDateGranularity: dateGranularity, primaryAxisGroupBySubFieldName: configuration.groupBySubFieldName ?? undefined, }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts index 7200f4a3fb..2618c12a74 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/formatDimensionValue.ts @@ -41,13 +41,16 @@ const normalizeMultiSelectValue = (value: unknown): unknown[] => { export const formatDimensionValue = ({ value, fieldMetadata, - dateGranularity = GRAPH_DEFAULT_DATE_GRANULARITY as ObjectRecordGroupByDateGranularity, + dateGranularity, subFieldName, }: FormatDimensionValueParams): string => { if (!isDefined(value)) { return t`Not Set`; } + const effectiveDateGranularity = (dateGranularity ?? + GRAPH_DEFAULT_DATE_GRANULARITY) as ObjectRecordGroupByDateGranularity; + switch (fieldMetadata.type) { case FieldMetadataType.SELECT: { const selectedOption = fieldMetadata.options?.find( @@ -75,21 +78,31 @@ export const formatDimensionValue = ({ case FieldMetadataType.DATE: case FieldMetadataType.DATE_TIME: { + const parsedDate = new Date(String(value)); + + if (isNaN(parsedDate.getTime())) { + return String(value); + } + if ( - dateGranularity === + effectiveDateGranularity === ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK || - dateGranularity === + effectiveDateGranularity === ObjectRecordGroupByDateGranularity.MONTH_OF_THE_YEAR || - dateGranularity === + effectiveDateGranularity === ObjectRecordGroupByDateGranularity.QUARTER_OF_THE_YEAR ) { return String(value); } - return formatDateByGranularity(new Date(String(value)), dateGranularity); + return formatDateByGranularity(parsedDate, effectiveDateGranularity); } case FieldMetadataType.RELATION: { if (isDefined(dateGranularity)) { + const parsedDate = new Date(String(value)); + if (isNaN(parsedDate.getTime())) { + return String(value); + } if ( dateGranularity === ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK || @@ -100,10 +113,7 @@ export const formatDimensionValue = ({ ) { return String(value); } - return formatDateByGranularity( - new Date(String(value)), - dateGranularity, - ); + return formatDateByGranularity(parsedDate, dateGranularity); } return String(value); } diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToLineChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToLineChartData.ts index 0706a75445..9697297a27 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToLineChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformGroupByDataToLineChartData.ts @@ -9,7 +9,8 @@ import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDim import { filterGroupByResults } from '@/page-layout/widgets/graph/utils/filterGroupByResults'; import { transformOneDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToLineChartData'; import { transformTwoDimensionalGroupByToLineChartData } from '@/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToLineChartData'; -import { isDefined } from 'twenty-shared/utils'; +import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType'; +import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; import { AxisNameDisplay, type LineChartConfiguration, @@ -18,6 +19,7 @@ import { type TransformGroupByDataToLineChartDataParams = { groupByData: Record | null | undefined; objectMetadataItem: ObjectMetadataItem; + objectMetadataItems: ObjectMetadataItem[]; configuration: LineChartConfiguration; aggregateOperation: string; }; @@ -45,6 +47,7 @@ const EMPTY_LINE_CHART_RESULT: TransformGroupByDataToLineChartDataResult = { export const transformGroupByDataToLineChartData = ({ groupByData, objectMetadataItem, + objectMetadataItems, configuration, aggregateOperation, }: TransformGroupByDataToLineChartDataParams): TransformGroupByDataToLineChartDataResult => { @@ -77,6 +80,8 @@ export const transformGroupByDataToLineChartData = ({ const primaryAxisSubFieldName = configuration.primaryAxisGroupBySubFieldName ?? undefined; + const secondaryAxisSubFieldName = + configuration.secondaryAxisGroupBySubFieldName ?? undefined; const queryResultGqlFieldName = getGroupByQueryResultGqlFieldName(objectMetadataItem); @@ -121,13 +126,49 @@ export const transformGroupByDataToLineChartData = ({ const showDataLabels = configuration.displayDataLabel ?? false; const showLegend = configuration.displayLegend ?? true; + const isDateField = isFieldMetadataDateKind(groupByFieldX.type); + const isNestedDateField = isNestedFieldDateType( + groupByFieldX, + primaryAxisSubFieldName, + objectMetadataItems, + ); + + const primaryAxisDateGranularity = + isDateField || isNestedDateField + ? configuration.primaryAxisDateGranularity + : undefined; + + const isSecondaryDateField = isDefined(groupByFieldY) + ? isFieldMetadataDateKind(groupByFieldY.type) + : false; + + const isSecondaryNestedDateField = + isDefined(groupByFieldY) && + isNestedFieldDateType( + groupByFieldY, + secondaryAxisSubFieldName, + objectMetadataItems, + ); + + const secondaryAxisDateGranularity = + isSecondaryDateField || isSecondaryNestedDateField + ? configuration.secondaryAxisGroupByDateGranularity + : undefined; + + const sanitizedConfiguration: LineChartConfiguration = { + ...configuration, + primaryAxisDateGranularity: primaryAxisDateGranularity ?? undefined, + secondaryAxisGroupByDateGranularity: + secondaryAxisDateGranularity ?? undefined, + }; + const baseResult = isDefined(groupByFieldY) ? transformTwoDimensionalGroupByToLineChartData({ rawResults: filteredResults, groupByFieldX, groupByFieldY, aggregateField, - configuration, + configuration: sanitizedConfiguration, aggregateOperation, objectMetadataItem, primaryAxisSubFieldName, @@ -136,7 +177,7 @@ export const transformGroupByDataToLineChartData = ({ rawResults: filteredResults, groupByFieldX, aggregateField, - configuration, + configuration: sanitizedConfiguration, aggregateOperation, objectMetadataItem, primaryAxisSubFieldName,