Convert number chart to aggregate chart allowing date aggregates (#15294)
- Convert number chart to aggregate chart - Allow date aggregates on all charts - Only allow `EARLIEST` and `LATEST` on Aggregate chart and Gauge chart - Various design fixes https://github.com/user-attachments/assets/b5a2239d-7b11-48b5-93e6-98ceffae5cab
This commit is contained in:
+38
-5
@@ -16,6 +16,7 @@ import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { isChartSettingDisabled } from '@/command-menu/pages/page-layout/utils/isChartSettingDisabled';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
|
||||
import { hasWidgetTooManyGroupsComponentState } from '@/page-layout/widgets/graph/states/hasWidgetTooManyGroupsComponentState';
|
||||
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
|
||||
@@ -23,9 +24,14 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { SidePanelInformationBanner } from 'twenty-ui/display';
|
||||
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
import {
|
||||
AggregateOperations,
|
||||
GraphType,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const StyledSidePanelInformationBanner = styled(SidePanelInformationBanner)`
|
||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
||||
@@ -41,6 +47,7 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
|
||||
const { setSelectedItemId } = useSelectableList(
|
||||
COMMAND_MENU_LIST_SELECTABLE_LIST_ID,
|
||||
);
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
if (widget.configuration?.__typename === 'IframeConfiguration') {
|
||||
throw new Error(t`IframeConfiguration is not supported`);
|
||||
@@ -69,11 +76,37 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
|
||||
useRecoilComponentState(hasWidgetTooManyGroupsComponentState);
|
||||
|
||||
const handleGraphTypeChange = (graphType: GraphType) => {
|
||||
const configToUpdate: Record<string, any> = {
|
||||
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
|
||||
graphType,
|
||||
};
|
||||
|
||||
if (graphType !== GraphType.AGGREGATE && graphType !== GraphType.GAUGE) {
|
||||
const currentAggregateFieldMetadataId =
|
||||
configuration.aggregateFieldMetadataId;
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widget.objectMetadataId,
|
||||
);
|
||||
|
||||
if (isDefined(objectMetadataItem)) {
|
||||
const aggregateField = objectMetadataItem.fields.find(
|
||||
(field) => field.id === currentAggregateFieldMetadataId,
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(aggregateField) &&
|
||||
isFieldMetadataDateKind(aggregateField.type) &&
|
||||
(configuration.aggregateOperation === AggregateOperations.MIN ||
|
||||
configuration.aggregateOperation === AggregateOperations.MAX)
|
||||
) {
|
||||
configToUpdate.aggregateOperation = AggregateOperations.COUNT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
|
||||
graphType,
|
||||
},
|
||||
configToUpdate,
|
||||
});
|
||||
|
||||
updateCommandMenuPageInfo({
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { MenuPicker } from 'twenty-ui/navigation';
|
||||
const graphTypeOptions = [
|
||||
GraphType.VERTICAL_BAR,
|
||||
GraphType.HORIZONTAL_BAR,
|
||||
GraphType.NUMBER,
|
||||
GraphType.AGGREGATE,
|
||||
GraphType.PIE,
|
||||
GraphType.LINE,
|
||||
GraphType.GAUGE,
|
||||
|
||||
+19
-3
@@ -3,6 +3,7 @@ import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/h
|
||||
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
|
||||
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
|
||||
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
@@ -22,7 +23,7 @@ import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { type AggregateOperations } from '~/generated/graphql';
|
||||
import { GraphType, type AggregateOperations } from '~/generated/graphql';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
|
||||
export const ChartAggregateOperationSelectionDropdownContent = ({
|
||||
@@ -40,7 +41,8 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'NumberChartConfiguration'
|
||||
widgetInEditMode?.configuration?.__typename !==
|
||||
'AggregateChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
@@ -71,7 +73,21 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
|
||||
})
|
||||
: [];
|
||||
|
||||
const aggregateOperationsWithLabels = availableAggregateOperations.map(
|
||||
const isAggregateOrGaugeChart =
|
||||
widgetInEditMode.configuration.graphType === GraphType.AGGREGATE ||
|
||||
widgetInEditMode.configuration.graphType === GraphType.GAUGE;
|
||||
|
||||
const filteredAggregateOperations = availableAggregateOperations.filter(
|
||||
(operation) => {
|
||||
return (
|
||||
isAggregateOrGaugeChart ||
|
||||
(operation !== DateAggregateOperations.EARLIEST &&
|
||||
operation !== DateAggregateOperations.LATEST)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const aggregateOperationsWithLabels = filteredAggregateOperations.map(
|
||||
(operation) => ({
|
||||
operation,
|
||||
label: getAggregateOperationLabel(operation),
|
||||
|
||||
+2
-8
@@ -14,7 +14,6 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
@@ -32,7 +31,7 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
|
||||
if (
|
||||
configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
configuration?.__typename !== 'LineChartConfiguration' &&
|
||||
configuration?.__typename !== 'NumberChartConfiguration'
|
||||
configuration?.__typename !== 'AggregateChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
@@ -61,12 +60,7 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
|
||||
searchQuery,
|
||||
getSearchableValues: (item) => [item.label, item.name],
|
||||
// TODO: remove the relation filter once group by is supported for relation fields
|
||||
}).filter(
|
||||
(field) =>
|
||||
!isFieldRelation(field) &&
|
||||
!field.isSystem &&
|
||||
!isFieldMetadataDateKind(field.type),
|
||||
);
|
||||
}).filter((field) => !isFieldRelation(field) && !field.isSystem);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { DATA_DISPLAY_AGGREGATE_SETTING } from '@/command-menu/pages/page-layout
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
|
||||
export const NUMBER_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
export const AGGREGATE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [
|
||||
+8
-8
@@ -1,19 +1,19 @@
|
||||
import { AGGREGATE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/AggregateChartSettings';
|
||||
import { GAUGE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/GaugeChartSettings';
|
||||
import { LINE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/LineChartSettings';
|
||||
import { NUMBER_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/NumberChartSettings';
|
||||
import { PIE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/PieChartSettings';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { getBarChartSettings } from '@/command-menu/pages/page-layout/utils/getBarChartSettings';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
Icon123,
|
||||
IconChartBar,
|
||||
IconChartBarHorizontal,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
type IconComponent,
|
||||
IconGauge,
|
||||
IconSum,
|
||||
} from 'twenty-ui/display';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
@@ -26,12 +26,12 @@ export const GRAPH_TYPE_INFORMATION: Record<
|
||||
}
|
||||
> = {
|
||||
[GraphType.VERTICAL_BAR]: {
|
||||
label: msg`Vertical Bar`,
|
||||
label: msg`Vertical`,
|
||||
icon: IconChartBar,
|
||||
settings: getBarChartSettings(GraphType.VERTICAL_BAR),
|
||||
},
|
||||
[GraphType.HORIZONTAL_BAR]: {
|
||||
label: msg`Horizontal Bar`,
|
||||
label: msg`Horizontal`,
|
||||
icon: IconChartBarHorizontal,
|
||||
settings: getBarChartSettings(GraphType.HORIZONTAL_BAR),
|
||||
},
|
||||
@@ -45,10 +45,10 @@ export const GRAPH_TYPE_INFORMATION: Record<
|
||||
icon: IconChartLine,
|
||||
settings: LINE_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.NUMBER]: {
|
||||
label: msg`Number`,
|
||||
icon: Icon123,
|
||||
settings: NUMBER_CHART_SETTINGS,
|
||||
[GraphType.AGGREGATE]: {
|
||||
label: msg`Aggregate`,
|
||||
icon: IconSum,
|
||||
settings: AGGREGATE_CHART_SETTINGS,
|
||||
},
|
||||
[GraphType.GAUGE]: {
|
||||
label: msg`Gauge`,
|
||||
|
||||
+1
-1
@@ -5,6 +5,6 @@ export const GRAPH_TYPE_TO_CONFIG_TYPENAME = {
|
||||
[GraphType.HORIZONTAL_BAR]: 'BarChartConfiguration',
|
||||
[GraphType.LINE]: 'LineChartConfiguration',
|
||||
[GraphType.PIE]: 'PieChartConfiguration',
|
||||
[GraphType.NUMBER]: 'NumberChartConfiguration',
|
||||
[GraphType.AGGREGATE]: 'AggregateChartConfiguration',
|
||||
[GraphType.GAUGE]: 'GaugeChartConfiguration',
|
||||
} as const;
|
||||
|
||||
+2
-2
@@ -2,11 +2,11 @@ import { ChartFieldSelectionForAggregateOperationDropdownContent } from '@/comma
|
||||
import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels';
|
||||
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
|
||||
import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { Icon123 } from 'twenty-ui/display';
|
||||
import { IconSum } from 'twenty-ui/display';
|
||||
|
||||
export const DATA_DISPLAY_AGGREGATE_SETTING: ChartSettingsItem = {
|
||||
isBoolean: false,
|
||||
Icon: Icon123,
|
||||
Icon: IconSum,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_AGGREGATE,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE,
|
||||
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
|
||||
|
||||
+8
-3
@@ -6,6 +6,7 @@ import { getChartAxisNameDisplayOptions } from '@/command-menu/pages/page-layout
|
||||
import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/utils/getFieldLabelWithSubField';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
|
||||
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
@@ -83,7 +84,11 @@ export const useChartSettingsValues = ({
|
||||
fieldMetadataItem.id === configuration.aggregateFieldMetadataId,
|
||||
);
|
||||
|
||||
const yAxisAggregateOperation = configuration.aggregateOperation;
|
||||
const aggregateOperation =
|
||||
convertAggregateOperationToExtendedAggregateOperation(
|
||||
configuration.aggregateOperation,
|
||||
aggregateField?.type,
|
||||
);
|
||||
|
||||
const xAxisOrderByLabel =
|
||||
isDefined(xAxisOrderBy) && isDefined(groupByFieldXId)
|
||||
@@ -131,11 +136,11 @@ export const useChartSettingsValues = ({
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y:
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE: {
|
||||
const hasAggregateLabel = isDefined(aggregateField?.label);
|
||||
const hasAggregateOperation = isDefined(yAxisAggregateOperation);
|
||||
const hasAggregateOperation = isDefined(aggregateOperation);
|
||||
|
||||
return `${aggregateField?.label ?? ''}${
|
||||
hasAggregateLabel && hasAggregateOperation
|
||||
? ` (${getAggregateOperationLabel(yAxisAggregateOperation)})`
|
||||
? ` (${getAggregateOperationLabel(aggregateOperation)})`
|
||||
: ''
|
||||
}`;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type AggregateChartConfiguration,
|
||||
type BarChartConfiguration,
|
||||
type GaugeChartConfiguration,
|
||||
type LineChartConfiguration,
|
||||
type NumberChartConfiguration,
|
||||
type PieChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
@@ -10,5 +10,5 @@ export type ChartConfiguration =
|
||||
| BarChartConfiguration
|
||||
| LineChartConfiguration
|
||||
| PieChartConfiguration
|
||||
| NumberChartConfiguration
|
||||
| AggregateChartConfiguration
|
||||
| GaugeChartConfiguration;
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type AggregateChartConfiguration,
|
||||
type BarChartConfiguration,
|
||||
type GaugeChartConfiguration,
|
||||
type LineChartConfiguration,
|
||||
type NumberChartConfiguration,
|
||||
type PieChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
@@ -10,5 +10,5 @@ export type ChartWidgetConfiguration =
|
||||
| BarChartConfiguration
|
||||
| GaugeChartConfiguration
|
||||
| LineChartConfiguration
|
||||
| NumberChartConfiguration
|
||||
| AggregateChartConfiguration
|
||||
| PieChartConfiguration;
|
||||
|
||||
+2
-2
@@ -88,8 +88,8 @@ const mixedGraphsPageLayoutMocks = {
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration',
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ type WidgetSizeConfig = {
|
||||
};
|
||||
|
||||
export const WIDGET_SIZES: Record<GraphType, WidgetSizeConfig> = {
|
||||
[GraphType.NUMBER]: {
|
||||
[GraphType.AGGREGATE]: {
|
||||
default: { w: 3, h: 2 },
|
||||
minimum: { w: 2, h: 2 },
|
||||
},
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
description
|
||||
filter
|
||||
}
|
||||
... on NumberChartConfiguration {
|
||||
... on AggregateChartConfiguration {
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
|
||||
+3
-3
@@ -1,6 +1,7 @@
|
||||
import { useCreatePageLayoutGraphWidget } from '@/page-layout/hooks/useCreatePageLayoutGraphWidget';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
PAGE_LAYOUT_TEST_INSTANCE_ID,
|
||||
PageLayoutTestWrapper,
|
||||
} from './PageLayoutTestWrapper';
|
||||
import { type GraphWidgetFieldSelection } from '@/page-layout/types/GraphWidgetFieldSelection';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => 'mock-uuid'),
|
||||
@@ -160,7 +160,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
});
|
||||
|
||||
const graphTypes = [
|
||||
GraphType.NUMBER,
|
||||
GraphType.AGGREGATE,
|
||||
GraphType.GAUGE,
|
||||
GraphType.PIE,
|
||||
GraphType.VERTICAL_BAR,
|
||||
@@ -169,7 +169,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
const mockFieldSelections: Partial<
|
||||
Record<GraphType, GraphWidgetFieldSelection>
|
||||
> = {
|
||||
[GraphType.NUMBER]: {
|
||||
[GraphType.AGGREGATE]: {
|
||||
objectMetadataId: 'test-object-id',
|
||||
aggregateFieldMetadataId: 'test-aggregate-field-id',
|
||||
},
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ describe('addWidgetToTab', () => {
|
||||
title: 'Test Widget',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
displayDataLabel: false,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ describe('convertLayoutsToWidgets', () => {
|
||||
columnSpan: 2,
|
||||
},
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
displayDataLabel: false,
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ describe('convertPageLayoutToTabLayouts', () => {
|
||||
title: 'Widget 1',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
displayDataLabel: false,
|
||||
|
||||
+3
-3
@@ -135,11 +135,11 @@ describe('extractFieldMetadataIdsFromWidget', () => {
|
||||
expect(result).toContain('field-2');
|
||||
});
|
||||
|
||||
it('should extract field IDs from NumberChartConfiguration', () => {
|
||||
it('should extract field IDs from AggregateChartConfiguration', () => {
|
||||
const widget = createMockWidget({
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration' as const,
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration' as const,
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateFieldMetadataId: 'field-1',
|
||||
aggregateOperation: AggregateOperations.AVG,
|
||||
displayDataLabel: false,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ describe('removeWidgetFromTab', () => {
|
||||
title: 'Widget 1',
|
||||
type: WidgetType.GRAPH,
|
||||
configuration: {
|
||||
graphType: GraphType.NUMBER,
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
displayDataLabel: false,
|
||||
|
||||
@@ -18,13 +18,13 @@ const createDefaultGraphConfiguration = (
|
||||
fieldSelection?: GraphWidgetFieldSelection,
|
||||
): WidgetConfiguration | null => {
|
||||
switch (graphType) {
|
||||
case GraphType.NUMBER:
|
||||
case GraphType.AGGREGATE:
|
||||
if (!isDefined(fieldSelection?.aggregateFieldMetadataId)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration',
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateFieldMetadataId: fieldSelection.aggregateFieldMetadataId,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
displayDataLabel: true,
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const extractFieldMetadataIdsFromWidget = (
|
||||
config.groupByFieldMetadataId,
|
||||
].filter(isDefined);
|
||||
|
||||
case 'NumberChartConfiguration':
|
||||
case 'AggregateChartConfiguration':
|
||||
return [config.aggregateFieldMetadataId].filter(isDefined);
|
||||
|
||||
case 'GaugeChartConfiguration':
|
||||
|
||||
@@ -2,7 +2,7 @@ import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getDefaultWidgetData = (graphType: GraphType) => {
|
||||
switch (graphType) {
|
||||
case GraphType.NUMBER:
|
||||
case GraphType.AGGREGATE:
|
||||
return {
|
||||
value: '1,234',
|
||||
trendPercentage: 15.2,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { GraphType } from '~/generated/graphql';
|
||||
|
||||
export const getWidgetTitle = (graphType: GraphType, index: number): string => {
|
||||
const baseNames: Record<GraphType, string> = {
|
||||
[GraphType.NUMBER]: 'Number',
|
||||
[GraphType.AGGREGATE]: 'Number',
|
||||
[GraphType.GAUGE]: 'Gauge',
|
||||
[GraphType.PIE]: 'Pie Chart',
|
||||
[GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
|
||||
|
||||
+6
-6
@@ -169,8 +169,8 @@ export const WithNumberChart: Story = {
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration',
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
displayDataLabel: true,
|
||||
@@ -277,8 +277,8 @@ export const SmallWidget: Story = {
|
||||
columnSpan: 2,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration',
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
displayDataLabel: true,
|
||||
@@ -409,8 +409,8 @@ export const WideWidget: Story = {
|
||||
columnSpan: 8,
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'NumberChartConfiguration',
|
||||
graphType: GraphType.NUMBER,
|
||||
__typename: 'AggregateChartConfiguration',
|
||||
graphType: GraphType.AGGREGATE,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
displayDataLabel: true,
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@ import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMeta
|
||||
import { getDefaultWidgetData } from '@/page-layout/utils/getDefaultWidgetData';
|
||||
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
|
||||
import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/ChartSkeletonLoader';
|
||||
import { GraphWidgetAggregateChartRenderer } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/components/GraphWidgetAggregateChartRenderer';
|
||||
import { GraphWidgetBarChartRenderer } from '@/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer';
|
||||
import { GraphWidgetNumberChartRenderer } from '@/page-layout/widgets/graph/graphWidgetNumberChart/components/GraphWidgetNumberChartRenderer';
|
||||
import { areChartConfigurationFieldsValidForQuery } from '@/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
|
||||
@@ -63,8 +63,8 @@ export const GraphWidget = ({
|
||||
}
|
||||
|
||||
switch (graphType) {
|
||||
case GraphType.NUMBER:
|
||||
return <GraphWidgetNumberChartRenderer widget={widget} />;
|
||||
case GraphType.AGGREGATE:
|
||||
return <GraphWidgetAggregateChartRenderer widget={widget} />;
|
||||
|
||||
case GraphType.GAUGE:
|
||||
return (
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react';
|
||||
|
||||
import { GraphWidgetAggregateChart } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/components/GraphWidgetAggregateChart';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { GraphWidgetNumberChart } from '@/page-layout/widgets/graph/graphWidgetNumberChart/components/GraphWidgetNumberChart';
|
||||
|
||||
const meta: Meta<typeof GraphWidgetNumberChart> = {
|
||||
title: 'Modules/PageLayout/Widgets/GraphWidgetNumberChart',
|
||||
component: GraphWidgetNumberChart,
|
||||
const meta: Meta<typeof GraphWidgetAggregateChart> = {
|
||||
title: 'Modules/PageLayout/Widgets/GraphWidgetAggregateChart',
|
||||
component: GraphWidgetAggregateChart,
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof GraphWidgetNumberChart>;
|
||||
type Story = StoryObj<typeof GraphWidgetAggregateChart>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
import { formatNumberChartTrend } from '@/page-layout/widgets/graph/graphWidgetNumberChart/utils/formatNumberChartTrend';
|
||||
import { formatNumberChartTrend } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/utils/formatNumberChartTrend';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
IconTrendingUp,
|
||||
} from 'twenty-ui/display';
|
||||
|
||||
type GraphWidgetNumberChartProps = {
|
||||
type GraphWidgetAggregateChartProps = {
|
||||
value: string | number;
|
||||
trendPercentage?: number;
|
||||
};
|
||||
@@ -36,13 +36,14 @@ const StyledTrendIconContainer = styled.div`
|
||||
`;
|
||||
|
||||
const StyledH1Title = styled(H1Title)`
|
||||
font-size: ${({ theme }) => theme.font.size.xxl};
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const GraphWidgetNumberChart = ({
|
||||
export const GraphWidgetAggregateChart = ({
|
||||
value,
|
||||
trendPercentage,
|
||||
}: GraphWidgetNumberChartProps) => {
|
||||
}: GraphWidgetAggregateChartProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const formattedPercentage = isDefined(trendPercentage)
|
||||
+7
-7
@@ -2,26 +2,26 @@ import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/Char
|
||||
import { useGraphWidgetAggregateQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import {
|
||||
type NumberChartConfiguration,
|
||||
type AggregateChartConfiguration,
|
||||
type PageLayoutWidget,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
const GraphWidgetNumberChart = lazy(() =>
|
||||
const GraphWidgetAggregateChart = lazy(() =>
|
||||
import(
|
||||
'@/page-layout/widgets/graph/graphWidgetNumberChart/components/GraphWidgetNumberChart'
|
||||
'@/page-layout/widgets/graph/graphWidgetAggregateChart/components/GraphWidgetAggregateChart'
|
||||
).then((module) => ({
|
||||
default: module.GraphWidgetNumberChart,
|
||||
default: module.GraphWidgetAggregateChart,
|
||||
})),
|
||||
);
|
||||
|
||||
export const GraphWidgetNumberChartRenderer = ({
|
||||
export const GraphWidgetAggregateChartRenderer = ({
|
||||
widget,
|
||||
}: {
|
||||
widget: PageLayoutWidget;
|
||||
}) => {
|
||||
const { value, loading } = useGraphWidgetAggregateQuery({
|
||||
objectMetadataItemId: widget.objectMetadataId,
|
||||
configuration: widget.configuration as NumberChartConfiguration,
|
||||
configuration: widget.configuration as AggregateChartConfiguration,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -30,7 +30,7 @@ export const GraphWidgetNumberChartRenderer = ({
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ChartSkeletonLoader />}>
|
||||
<GraphWidgetNumberChart value={value ?? 0} />
|
||||
<GraphWidgetAggregateChart value={value ?? '-'} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
+12
-8
@@ -1,11 +1,11 @@
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
|
||||
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
|
||||
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
|
||||
import { useGraphWidgetQueryCommon } from '@/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon';
|
||||
import { UserContext } from '@/users/contexts/UserContext';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type NumberChartConfiguration } from '~/generated/graphql';
|
||||
import { type AggregateChartConfiguration } from '~/generated/graphql';
|
||||
import { dateLocaleState } from '~/localization/states/dateLocaleState';
|
||||
|
||||
export const useGraphWidgetAggregateQuery = ({
|
||||
@@ -13,7 +13,7 @@ export const useGraphWidgetAggregateQuery = ({
|
||||
configuration,
|
||||
}: {
|
||||
objectMetadataItemId: string;
|
||||
configuration: NumberChartConfiguration;
|
||||
configuration: AggregateChartConfiguration;
|
||||
}) => {
|
||||
const { objectMetadataItem, gqlOperationFilter, aggregateField } =
|
||||
useGraphWidgetQueryCommon({
|
||||
@@ -21,14 +21,18 @@ export const useGraphWidgetAggregateQuery = ({
|
||||
configuration,
|
||||
});
|
||||
|
||||
// TODO: Move this enum to shared
|
||||
const aggregateOperation =
|
||||
configuration.aggregateOperation as unknown as ExtendedAggregateOperations;
|
||||
const aggregateOperation = configuration.aggregateOperation;
|
||||
|
||||
const extendedAggregateOperation =
|
||||
convertAggregateOperationToExtendedAggregateOperation(
|
||||
aggregateOperation,
|
||||
aggregateField.type,
|
||||
);
|
||||
|
||||
const { data, loading, error } = useAggregateRecords({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordGqlFieldsAggregate: {
|
||||
[aggregateField.name]: [aggregateOperation],
|
||||
[aggregateField.name]: [extendedAggregateOperation],
|
||||
},
|
||||
filter: gqlOperationFilter,
|
||||
});
|
||||
@@ -40,7 +44,7 @@ export const useGraphWidgetAggregateQuery = ({
|
||||
data,
|
||||
objectMetadataItem,
|
||||
fieldMetadataId: configuration.aggregateFieldMetadataId,
|
||||
aggregateOperation,
|
||||
aggregateOperation: extendedAggregateOperation,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import {
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import {
|
||||
type AggregateChartConfiguration,
|
||||
type BarChartConfiguration,
|
||||
type NumberChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
|
||||
export const useGraphWidgetQueryCommon = ({
|
||||
@@ -13,7 +13,7 @@ export const useGraphWidgetQueryCommon = ({
|
||||
configuration,
|
||||
}: {
|
||||
objectMetadataItemId: string;
|
||||
configuration: BarChartConfiguration | NumberChartConfiguration;
|
||||
configuration: BarChartConfiguration | AggregateChartConfiguration;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: objectMetadataItemId,
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ export const areChartConfigurationFieldsValidForQuery = (
|
||||
fieldExists(configuration.groupByFieldMetadataId, objectMetadataItem)
|
||||
);
|
||||
|
||||
case 'NumberChartConfiguration':
|
||||
case 'AggregateChartConfiguration':
|
||||
case 'GaugeChartConfiguration':
|
||||
return fieldExists(
|
||||
configuration.aggregateFieldMetadataId,
|
||||
|
||||
Reference in New Issue
Block a user