[BREAKING_CHANGE_DASHBOARDS][DASHBOARD_CACHE_FLUSH_REQUIRED] Refactor page layout widget configuration type (#16671)

# Introduction
In this pull-request we're refactoring the page layout widget
configuration entity to be containing its discriminated key simplifying
underlying code and maintainability
- Made the configuration and title non nullable
- Introduced a generic predicate for the `widgetConfigurationType`
- Upgraded command to remove `graphType` and insert new
`configurationType` to existing entries
- Migrated frontend to new type system

---------

Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
Paul Rastoin
2025-12-29 15:46:59 +01:00
committed by GitHub
parent 2b9728230a
commit 4135a6473a
221 changed files with 3241 additions and 1947 deletions
@@ -1,5 +1,6 @@
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
import { getCurrentGraphTypeFromConfig } from '@/command-menu/pages/page-layout/utils/getCurrentGraphTypeFromConfig';
import { isWidgetConfigurationOfTypeGraph } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfTypeGraph';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
@@ -116,11 +117,13 @@ export const usePageLayoutHeaderInfo = ({
return null;
}
if (!isChartWidget(widgetInEditMode)) {
if (!isWidgetConfigurationOfTypeGraph(widgetInEditMode.configuration)) {
return null;
}
const currentGraphType = widgetInEditMode.configuration.graphType;
const currentGraphType = getCurrentGraphTypeFromConfig(
widgetInEditMode.configuration,
);
const graphTypeInfo = GRAPH_TYPE_INFORMATION[currentGraphType];
const graphTypeLabel = t(graphTypeInfo.label);
@@ -1,11 +1,10 @@
import { getChartLimitMessage } from '@/command-menu/pages/page-layout/utils/getChartLimitMessage';
import { t } from '@lingui/core/macro';
import { SidePanelInformationBanner } from 'twenty-ui/display';
import { type GraphType } from '~/generated/graphql';
import { type WidgetConfigurationType } from '~/generated/graphql';
type ChartLimitInfoBannerProps = {
graphType: GraphType;
widgetConfigurationType: WidgetConfigurationType;
isPrimaryAxisDate: boolean;
primaryAxisDateGranularity: Parameters<
typeof getChartLimitMessage
@@ -13,14 +12,14 @@ type ChartLimitInfoBannerProps = {
};
export const ChartLimitInfoBanner = ({
graphType,
widgetConfigurationType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
}: ChartLimitInfoBannerProps) => {
return (
<SidePanelInformationBanner
message={getChartLimitMessage({
graphType,
widgetConfigurationType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
})}
@@ -19,11 +19,11 @@ import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { assertChartWidgetOrThrow } from '@/command-menu/pages/page-layout/utils/assertChartWidgetOrThrow';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { getCurrentGraphTypeFromConfig } from '@/command-menu/pages/page-layout/utils/getCurrentGraphTypeFromConfig';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { GraphType } from '~/generated/graphql';
const StyledCommandMenuContainer = styled.div`
display: flex;
@@ -42,7 +42,6 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
assertChartWidgetOrThrow(widget);
const configuration = widget.configuration;
const currentGraphType = configuration?.graphType;
const { getChartSettingsValues } = useChartSettingsValues({
objectMetadataId: widget.objectMetadataId,
@@ -81,6 +80,8 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
}
};
const currentGraphType = getCurrentGraphTypeFromConfig(configuration);
const chartSettings = GRAPH_TYPE_INFORMATION[currentGraphType].settings;
const objectMetadataItem = objectMetadataItems.find(
@@ -103,11 +104,18 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
.map((item) => item.id),
);
const primaryAxisFieldMetadataId = isBarOrLineChartConfiguration(
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
const isPieChart = isWidgetConfigurationOfType(
configuration,
)
'PieChartConfiguration',
);
const primaryAxisFieldMetadataId = isBarOrLineChart
? configuration.primaryAxisGroupByFieldMetadataId
: isPieChartConfiguration(configuration)
: isPieChart
? configuration.groupByFieldMetadataId
: null;
@@ -117,11 +125,9 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const isPrimaryAxisDate = isFieldMetadataDateKind(primaryAxisField?.type);
const primaryAxisDateGranularity = isBarOrLineChartConfiguration(
configuration,
)
const primaryAxisDateGranularity = isBarOrLineChart
? configuration.primaryAxisDateGranularity
: isPieChartConfiguration(configuration)
: isPieChart
? configuration.dateGranularity
: null;
@@ -156,7 +162,7 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
<CommandGroup key={group.heading.id} heading={t(group.heading)}>
{shouldShowBanner && hasWidgetTooManyGroups && (
<ChartLimitInfoBanner
graphType={currentGraphType}
widgetConfigurationType={configuration.configurationType}
isPrimaryAxisDate={isPrimaryAxisDate}
primaryAxisDateGranularity={primaryAxisDateGranularity}
/>
@@ -1,8 +1,9 @@
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { FeatureFlagKey, GraphType } from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { t } from '@lingui/core/macro';
import { MenuPicker } from 'twenty-ui/navigation';
@@ -1,6 +1,7 @@
import { ChartSettings } from '@/command-menu/pages/page-layout/components/ChartSettings';
import { WidgetSettingsFooter } from '@/command-menu/pages/page-layout/components/WidgetSettingsFooter';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
import { WidgetComponentInstanceContext } from '@/page-layout/widgets/states/contexts/WidgetComponentInstanceContext';
@@ -31,11 +32,7 @@ export const CommandMenuPageLayoutChartSettings = () => {
.flatMap((tab) => tab.widgets)
.find((widget) => widget.id === pageLayoutEditingWidgetId);
if (
!isDefined(widgetInEditMode) ||
!isDefined(widgetInEditMode.configuration) ||
!('graphType' in widgetInEditMode.configuration)
) {
if (!isDefined(widgetInEditMode) || !isChartWidget(widgetInEditMode)) {
return null;
}
@@ -7,6 +7,7 @@ import { t } from '@lingui/core/macro';
import { isNonEmptyString, isString } from '@sniptt/guards';
import { useState } from 'react';
import { isDefined, isValidUrl } from 'twenty-shared/utils';
import { WidgetConfigurationType } from '~/generated/graphql';
const StyledContainer = styled.div`
display: flex;
@@ -67,6 +68,7 @@ export const CommandMenuPageLayoutIframeSettings = () => {
updatePageLayoutWidget(widgetInEditMode.id, {
configuration: {
__typename: 'IframeConfiguration',
configurationType: WidgetConfigurationType.IFRAME,
url: isNonEmptyString(trimmedValue) ? trimmedValue : null,
},
});
@@ -19,7 +19,6 @@ import {
IconChartPie,
IconFrame,
} from 'twenty-ui/display';
import { GraphType } from '~/generated-metadata/graphql';
export const CommandMenuPageLayoutWidgetTypeSelect = () => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
@@ -49,7 +48,6 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
if (!isDefined(pageLayoutEditingWidgetId)) {
const fieldSelection = buildBarChartFieldSelection();
const newWidget = createPageLayoutGraphWidget({
graphType: GraphType.VERTICAL_BAR,
fieldSelection,
});
@@ -1,7 +1,7 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio';
@@ -53,7 +53,7 @@ export const ChartAggregateOperationSelectableListItem = ({
configuration.aggregateOperation;
const isCurrentlyRatio =
isAggregateChartConfiguration(configuration) &&
isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration') &&
isDefined(configuration.ratioAggregateConfig);
if (!isExtendedAggregateOperation(operation)) {
@@ -3,9 +3,7 @@ import { ChartRatioAggregateOperationSelectableListItem } from '@/command-menu/p
import { ChartRatioOptionValueSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
@@ -42,13 +40,19 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
const configuration = widgetInEditMode?.configuration;
const isAggregateChart = isAggregateChartConfiguration(configuration);
const isAggregateChart = isWidgetConfigurationOfType(
configuration,
'AggregateChartConfiguration',
);
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
if (
!isBarOrLineChartConfiguration(configuration) &&
!isAggregateChart &&
!isPieChartConfiguration(configuration)
) {
if (!isBarOrLineChart && !isAggregateChart && !isPieChart) {
throw new Error('Invalid configuration type');
}
@@ -2,7 +2,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { getChartAxisNameDisplayOptions } from '@/command-menu/pages/page-layout/utils/getChartAxisNameDisplayOptions';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -18,11 +18,17 @@ export const ChartAxisNameSelectionDropdownContent = () => {
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
if (!isBarOrLineChartConfiguration(widgetInEditMode?.configuration)) {
const configuration = widgetInEditMode?.configuration;
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (!isBarOrLineChart) {
throw new Error('Invalid configuration type');
}
const currentAxisNameDisplay = widgetInEditMode.configuration.axisNameDisplay;
const currentAxisNameDisplay = configuration.axisNameDisplay;
const dropdownId = useAvailableComponentInstanceIdOrThrow(
DropdownComponentInstanceContext,
@@ -4,10 +4,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isGaugeChartConfiguration } from '@/command-menu/pages/page-layout/utils/isGaugeChartConfiguration';
import { isIframeConfiguration } from '@/command-menu/pages/page-layout/utils/isIframeConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
@@ -52,17 +49,30 @@ export const ChartColorSelectionDropdownContent = () => {
return null;
}
if (isIframeConfiguration(widgetInEditMode.configuration)) {
if (
isWidgetConfigurationOfType(
widgetInEditMode.configuration,
'IframeConfiguration',
)
) {
throw new Error('Invalid configuration type');
}
const configuration = widgetInEditMode.configuration as ChartConfiguration;
if (
!isBarOrLineChartConfiguration(configuration) &&
!isGaugeChartConfiguration(configuration) &&
!isPieChartConfiguration(configuration)
) {
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
const isGaugeChart = isWidgetConfigurationOfType(
configuration,
'GaugeChartConfiguration',
);
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
if (!isBarOrLineChart && !isGaugeChart && !isPieChart) {
return null;
}
@@ -3,9 +3,9 @@ import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/h
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { getDateGranularityLabel } from '@/command-menu/pages/page-layout/utils/getDateGranularityLabel';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isChartConfiguration } from '@/command-menu/pages/page-layout/utils/isChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -17,6 +17,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type WidgetConfiguration } from '~/generated/graphql';
type ChartDateGranularitySelectionDropdownContentProps = {
axis?: 'primary' | 'secondary';
@@ -31,11 +32,15 @@ const getCurrentDateGranularity = ({
}) => {
const defaultGranularity = ObjectRecordGroupByDateGranularity.DAY;
if (isPieChartConfiguration(configuration)) {
if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
return configuration.dateGranularity || defaultGranularity;
}
if (!isBarOrLineChartConfiguration(configuration)) {
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (!isBarOrLineChart) {
return defaultGranularity;
}
@@ -48,6 +53,23 @@ const getCurrentDateGranularity = ({
);
};
const isChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldsConfiguration
| FieldConfiguration
| null
| undefined,
): configuration is ChartConfiguration => {
return (
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'PieChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'GaugeChartConfiguration')
);
};
export const ChartDateGranularitySelectionDropdownContent = ({
axis,
}: ChartDateGranularitySelectionDropdownContentProps) => {
@@ -60,11 +82,19 @@ export const ChartDateGranularitySelectionDropdownContent = ({
throw new Error('Invalid configuration type');
}
if (!isDefined(axis) && !isPieChartConfiguration(configuration)) {
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (!isDefined(axis) && !isPieChart) {
throw new Error('Invalid configuration type');
}
if (isDefined(axis) && !isBarOrLineChartConfiguration(configuration)) {
if (isDefined(axis) && !isBarOrLineChart) {
throw new Error('Invalid configuration type');
}
@@ -1,9 +1,7 @@
import { ChartAggregateOperationSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -31,11 +29,19 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
const configuration = widgetInEditMode?.configuration;
if (
!isBarOrLineChartConfiguration(configuration) &&
!isAggregateChartConfiguration(configuration) &&
!isPieChartConfiguration(configuration)
) {
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
const isAggregateChart = isWidgetConfigurationOfType(
configuration,
'AggregateChartConfiguration',
);
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
if (!isBarOrLineChart && !isAggregateChart && !isPieChart) {
throw new Error('Invalid configuration type');
}
@@ -1,13 +1,11 @@
import { ChartGroupByFieldSelectionDropdownContentBase } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase';
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
import { type TypedLineChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedLineChartConfiguration';
export const ChartGroupByFieldSelectionDropdownContent = () => {
return (
<ChartGroupByFieldSelectionDropdownContentBase<
BarChartConfiguration | LineChartConfiguration
TypedBarChartConfiguration | TypedLineChartConfiguration
>
fieldMetadataIdKey="secondaryAxisGroupByFieldMetadataId"
subFieldNameKey="secondaryAxisGroupBySubFieldName"
@@ -1,9 +1,9 @@
import { ChartGroupByFieldSelectionDropdownContentBase } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase';
import { type PieChartConfiguration } from '~/generated/graphql';
import { type TypedPieChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedPieChartConfiguration';
export const ChartPieFieldSelectionDropdownContent = () => {
return (
<ChartGroupByFieldSelectionDropdownContentBase<PieChartConfiguration>
<ChartGroupByFieldSelectionDropdownContentBase<TypedPieChartConfiguration>
fieldMetadataIdKey="groupByFieldMetadataId"
subFieldNameKey="groupBySubFieldName"
/>
@@ -1,6 +1,6 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DASHBOARD_AGGREGATE_OPERATION_RATIO } from '@/page-layout/widgets/graph/constants/DashboardAggregateOperationRatio';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -30,8 +30,10 @@ export const ChartRatioAggregateOperationSelectableListItem = ({
);
const isCurrentlyRatio =
isAggregateChartConfiguration(widgetInEditMode?.configuration) &&
isDefined(widgetInEditMode.configuration.ratioAggregateConfig);
isWidgetConfigurationOfType(
widgetInEditMode?.configuration,
'AggregateChartConfiguration',
) && isDefined(widgetInEditMode.configuration.ratioAggregateConfig);
const isFocused = selectedItemId === DASHBOARD_AGGREGATE_OPERATION_RATIO;
@@ -1,7 +1,7 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -36,8 +36,9 @@ export const ChartRatioOptionBooleanSelectableListItem = ({
dropdownId,
);
const currentRatioConfig = isAggregateChartConfiguration(
const currentRatioConfig = isWidgetConfigurationOfType(
widgetInEditMode?.configuration,
'AggregateChartConfiguration',
)
? widgetInEditMode.configuration.ratioAggregateConfig
: undefined;
@@ -1,7 +1,7 @@
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -38,8 +38,9 @@ export const ChartRatioOptionSelectSelectableListItem = ({
dropdownId,
);
const currentRatioConfig = isAggregateChartConfiguration(
const currentRatioConfig = isWidgetConfigurationOfType(
widgetInEditMode?.configuration,
'AggregateChartConfiguration',
)
? widgetInEditMode.configuration.ratioAggregateConfig
: undefined;
@@ -2,7 +2,7 @@ import { ChartRatioOptionBooleanSelectableListItem } from '@/command-menu/pages/
import { ChartRatioOptionSelectSelectableListItem } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionSelectSelectableListItem';
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
@@ -42,7 +42,12 @@ export const ChartRatioOptionValueSelectionDropdownContent = ({
DropdownComponentInstanceContext,
);
if (!isAggregateChartConfiguration(widgetInEditMode?.configuration)) {
if (
!isWidgetConfigurationOfType(
widgetInEditMode?.configuration,
'AggregateChartConfiguration',
)
) {
return null;
}
@@ -3,7 +3,7 @@ import { useGraphGroupBySortOptionLabels } from '@/command-menu/pages/page-layou
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownComponentInstanceContext } from '@/ui/layout/dropdown/contexts/DropdownComponentInstanceContext';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
@@ -23,7 +23,9 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
const configuration = widgetInEditMode?.configuration;
const isBarOrLineChart = isBarOrLineChartConfiguration(configuration);
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (!isBarOrLineChart) {
throw new Error('Invalid configuration type');
@@ -3,9 +3,7 @@ import { useGraphXSortOptionLabels } from '@/command-menu/pages/page-layout/hook
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { isBarChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarChartConfiguration';
import { isLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -30,9 +28,18 @@ export const ChartSortBySelectionDropdownContent = () => {
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const configuration = widgetInEditMode?.configuration;
const isPieChart = isPieChartConfiguration(configuration);
const isLineChart = isLineChartConfiguration(configuration);
const isBarChart = isBarChartConfiguration(configuration);
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
const isLineChart = isWidgetConfigurationOfType(
configuration,
'LineChartConfiguration',
);
const isBarChart = isWidgetConfigurationOfType(
configuration,
'BarChartConfiguration',
);
if (!isBarChart && !isLineChart && !isPieChart) {
throw new Error('Invalid configuration type');
@@ -1,13 +1,11 @@
import { ChartGroupByFieldSelectionDropdownContentBase } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase';
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
import { type TypedLineChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedLineChartConfiguration';
export const ChartXAxisFieldSelectionDropdownContent = () => {
return (
<ChartGroupByFieldSelectionDropdownContentBase<
BarChartConfiguration | LineChartConfiguration
TypedBarChartConfiguration | TypedLineChartConfiguration
>
fieldMetadataIdKey="primaryAxisGroupByFieldMetadataId"
subFieldNameKey="primaryAxisGroupBySubFieldName"
@@ -0,0 +1,11 @@
import { WidgetConfigurationType } from '~/generated/graphql';
export const GRAPH_CONFIGURATION_TYPE_TO_CONFIG_TYPENAME: Partial<
Record<WidgetConfigurationType, string>
> = {
[WidgetConfigurationType.BAR_CHART]: 'BarChartConfiguration',
[WidgetConfigurationType.LINE_CHART]: 'LineChartConfiguration',
[WidgetConfigurationType.PIE_CHART]: 'PieChartConfiguration',
[WidgetConfigurationType.AGGREGATE_CHART]: 'AggregateChartConfiguration',
[WidgetConfigurationType.GAUGE_CHART]: 'GaugeChartConfiguration',
} as const;
@@ -3,6 +3,7 @@ import { GAUGE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants
import { LINE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/LineChartSettings';
import { PIE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/PieChartSettings';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { getBarChartSettings } from '@/command-menu/pages/page-layout/utils/getBarChartSettings';
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
@@ -15,7 +16,7 @@ import {
IconGauge,
IconSum,
} from 'twenty-ui/display';
import { GraphType } from '~/generated-metadata/graphql';
import { BarChartLayout } from '~/generated/graphql';
export const GRAPH_TYPE_INFORMATION: Record<
GraphType,
@@ -28,12 +29,12 @@ export const GRAPH_TYPE_INFORMATION: Record<
[GraphType.VERTICAL_BAR]: {
label: msg`Vertical Bar Chart`,
icon: IconChartBar,
settings: getBarChartSettings(GraphType.VERTICAL_BAR),
settings: getBarChartSettings(BarChartLayout.VERTICAL),
},
[GraphType.HORIZONTAL_BAR]: {
label: msg`Horizontal Bar Chart`,
icon: IconChartBarHorizontal,
settings: getBarChartSettings(GraphType.HORIZONTAL_BAR),
settings: getBarChartSettings(BarChartLayout.HORIZONTAL),
},
[GraphType.PIE]: {
label: msg`Pie Chart`,
@@ -1,10 +0,0 @@
import { GraphType } from '~/generated-metadata/graphql';
export const GRAPH_TYPE_TO_CONFIG_TYPENAME = {
[GraphType.VERTICAL_BAR]: 'BarChartConfiguration',
[GraphType.HORIZONTAL_BAR]: 'BarChartConfiguration',
[GraphType.LINE]: 'LineChartConfiguration',
[GraphType.PIE]: 'PieChartConfiguration',
[GraphType.AGGREGATE]: 'AggregateChartConfiguration',
[GraphType.GAUGE]: 'GaugeChartConfiguration',
} as const;
@@ -3,16 +3,17 @@ import { RecoilRoot } from 'recoil';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import {
AggregateOperations,
AxisNameDisplay,
type BarChartConfiguration,
BarChartGroupMode,
BarChartLayout,
FieldMetadataType,
GraphOrderBy,
GraphType,
WidgetConfigurationType,
} from '~/generated-metadata/graphql';
import { useChartSettingsValues } from '@/command-menu/pages/page-layout/hooks/useChartSettingsValues';
@@ -59,13 +60,12 @@ const mockObjectMetadataItem: ObjectMetadataItem = {
} as ObjectMetadataItem;
const buildBarChartConfiguration = (
overrides: Partial<BarChartConfiguration>,
): BarChartConfiguration =>
overrides: Partial<TypedBarChartConfiguration>,
): TypedBarChartConfiguration =>
({
__typename: 'BarChartConfiguration',
aggregateFieldMetadataId: 'field-amount',
aggregateOperation: AggregateOperations.SUM,
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
primaryAxisGroupBySubFieldName: null,
secondaryAxisGroupByFieldMetadataId: null,
@@ -76,7 +76,7 @@ const buildBarChartConfiguration = (
displayDataLabel: false,
groupMode: 'GROUPED',
...overrides,
}) as BarChartConfiguration;
}) as TypedBarChartConfiguration;
const renderUseChartSettingsValues = (configuration: ChartConfiguration) => {
return renderHook(
@@ -102,7 +102,6 @@ const renderUseChartSettingsValues = (configuration: ChartConfiguration) => {
describe('useChartSettingsValues', () => {
describe('Vertical bar chart (1D - single grouping)', () => {
const verticalBarConfig = buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
secondaryAxisGroupByFieldMetadataId: null,
});
@@ -172,7 +171,7 @@ describe('useChartSettingsValues', () => {
describe('Horizontal bar chart (1D - single grouping)', () => {
const horizontalBarConfig = buildBarChartConfiguration({
graphType: GraphType.HORIZONTAL_BAR,
layout: BarChartLayout.HORIZONTAL,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
secondaryAxisGroupByFieldMetadataId: null,
});
@@ -213,7 +212,7 @@ describe('useChartSettingsValues', () => {
describe('Vertical bar chart (2D - with secondary grouping)', () => {
const verticalBar2DConfig = buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
layout: BarChartLayout.VERTICAL,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
secondaryAxisGroupByFieldMetadataId: 'field-stage',
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
@@ -253,7 +252,7 @@ describe('useChartSettingsValues', () => {
describe('Horizontal bar chart (2D - with secondary grouping)', () => {
const horizontalBar2DConfig = buildBarChartConfiguration({
graphType: GraphType.HORIZONTAL_BAR,
layout: BarChartLayout.HORIZONTAL,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
secondaryAxisGroupByFieldMetadataId: 'field-stage',
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
@@ -282,7 +281,7 @@ describe('useChartSettingsValues', () => {
describe('Composite field with subfield', () => {
const configWithSubField = buildBarChartConfiguration({
graphType: GraphType.VERTICAL_BAR,
layout: BarChartLayout.VERTICAL,
primaryAxisGroupByFieldMetadataId: 'field-full-name',
primaryAxisGroupBySubFieldName: 'firstName',
});
@@ -345,7 +344,7 @@ describe('useChartSettingsValues', () => {
it('should return capitalized color when color is set', () => {
const config = buildBarChartConfiguration({
color: 'blue',
} as Partial<BarChartConfiguration>);
});
const { result } = renderUseChartSettingsValues(config);
@@ -525,7 +524,7 @@ describe('useChartSettingsValues', () => {
describe('Critical: Proves no swapping occurs', () => {
it('horizontal bar with NULL secondary should still return primary for DATA_ON_DISPLAY_X', () => {
const horizontalConfig = buildBarChartConfiguration({
graphType: GraphType.HORIZONTAL_BAR,
layout: BarChartLayout.HORIZONTAL,
primaryAxisGroupByFieldMetadataId: 'field-company-name',
secondaryAxisGroupByFieldMetadataId: null,
});
@@ -554,12 +553,14 @@ describe('useChartSettingsValues', () => {
const verticalConfig = buildBarChartConfiguration({
...baseConfig,
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
});
const horizontalConfig = buildBarChartConfiguration({
...baseConfig,
graphType: GraphType.HORIZONTAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.HORIZONTAL,
});
const { result: verticalResult } =
@@ -1,10 +1,12 @@
import { GRAPH_TYPE_TO_CONFIG_TYPENAME } from '@/command-menu/pages/page-layout/constants/GraphTypeToConfigTypename';
import { GRAPH_CONFIGURATION_TYPE_TO_CONFIG_TYPENAME } from '@/command-menu/pages/page-layout/constants/GraphConfigurationTypeToConfigTypename';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { convertAggregateOperationForDateField } from '@/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField';
import { convertBarOrLineChartConfigToPieChart } from '@/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart';
import { convertPieChartConfigToBarOrLineChart } from '@/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { getConfigurationTypeFromGraphType } from '@/command-menu/pages/page-layout/utils/getConfigurationTypeFromGraphType';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { isWidgetConfigurationOfTypeGraph } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfTypeGraph';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
@@ -16,7 +18,7 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { GraphType } from '~/generated/graphql';
import { BarChartLayout } from '~/generated/graphql';
export const useGetConfigToUpdateAfterGraphTypeChange = ({
pageLayoutId,
@@ -75,13 +77,20 @@ export const useGetConfigToUpdateAfterGraphTypeChange = ({
throw new Error('Widget configuration not found in draft state');
}
const currentConfiguration =
widgetInDraft.configuration as ChartConfiguration;
const currentConfiguration = widgetInDraft.configuration;
let configToUpdate: Record<string, any> = {
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
graphType,
};
if (!isWidgetConfigurationOfTypeGraph(currentConfiguration)) {
throw new Error('Widget configuration is not a chart configuration');
}
const newConfigurationType =
getConfigurationTypeFromGraphType(graphType);
let configToUpdate = {
__typename:
GRAPH_CONFIGURATION_TYPE_TO_CONFIG_TYPENAME[newConfigurationType],
configurationType: newConfigurationType,
} as Partial<ChartConfiguration>;
if (
graphType !== GraphType.AGGREGATE &&
@@ -106,13 +115,26 @@ export const useGetConfigToUpdateAfterGraphTypeChange = ({
}
const isPieChart = graphType === GraphType.PIE;
const isVerticalBarChart = graphType === GraphType.VERTICAL_BAR;
const isHorizontalBarChart = graphType === GraphType.HORIZONTAL_BAR;
const isLineChart = graphType === GraphType.LINE;
const isBarOrLineChart =
graphType === GraphType.VERTICAL_BAR ||
graphType === GraphType.HORIZONTAL_BAR ||
graphType === GraphType.LINE;
isVerticalBarChart || isHorizontalBarChart || isLineChart;
const wasBarOrLineChart =
isBarOrLineChartConfiguration(currentConfiguration);
const wasPieChart = isPieChartConfiguration(currentConfiguration);
isWidgetConfigurationOfType(
currentConfiguration,
'BarChartConfiguration',
) ||
isWidgetConfigurationOfType(
currentConfiguration,
'LineChartConfiguration',
);
const wasPieChart = isWidgetConfigurationOfType(
currentConfiguration,
'PieChartConfiguration',
);
if (isPieChart && wasBarOrLineChart) {
configToUpdate = {
@@ -128,6 +150,20 @@ export const useGetConfigToUpdateAfterGraphTypeChange = ({
};
}
if (isHorizontalBarChart) {
configToUpdate = {
...configToUpdate,
layout: BarChartLayout.HORIZONTAL,
};
}
if (isVerticalBarChart) {
configToUpdate = {
...configToUpdate,
layout: BarChartLayout.VERTICAL,
};
}
const activeTabId = snapshot.getLoadable(activeTabIdState).getValue();
if (isDefined(activeTabId) && isDefined(currentlyEditingWidgetId)) {
@@ -135,12 +171,12 @@ export const useGetConfigToUpdateAfterGraphTypeChange = ({
.getLoadable(pageLayoutCurrentLayoutsState)
.getValue();
const updatedLayouts = updateWidgetMinimumSizeForGraphType(
graphType,
currentlyEditingWidgetId,
activeTabId,
const updatedLayouts = updateWidgetMinimumSizeForGraphType({
configurationType: newConfigurationType,
widgetId: currentlyEditingWidgetId,
tabId: activeTabId,
currentLayouts,
);
});
set(pageLayoutCurrentLayoutsState, updatedLayouts);
}
@@ -1,14 +1,12 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type LineChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
import { type TypedGaugeChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedGaugeChartConfiguration';
import { type TypedAggregateChartConfiguration } from './TypedAggregateChartConfiguration';
import { type TypedBarChartConfiguration } from './TypedBarChartConfiguration';
import { type TypedLineChartConfiguration } from './TypedLineChartConfiguration';
import { type TypedPieChartConfiguration } from './TypedPieChartConfiguration';
export type ChartConfiguration =
| BarChartConfiguration
| LineChartConfiguration
| PieChartConfiguration
| AggregateChartConfiguration
| GaugeChartConfiguration;
| TypedBarChartConfiguration
| TypedLineChartConfiguration
| TypedPieChartConfiguration
| TypedAggregateChartConfiguration
| TypedGaugeChartConfiguration;
@@ -1,4 +1,3 @@
import { type ChartFilters } from '@/command-menu/pages/page-layout/types/ChartFilters';
import { type ChartWidgetConfiguration } from '@/command-menu/pages/page-layout/types/ChartWidgetConfiguration';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { type ModifiedProperties } from 'twenty-shared/types';
@@ -8,9 +7,6 @@ export type ChartWidget = ModifiedProperties<
PageLayoutWidget,
{
type: WidgetType.GRAPH;
configuration: ModifiedProperties<
ChartWidgetConfiguration,
{ filter?: ChartFilters }
>;
configuration: ChartWidgetConfiguration;
}
>;
@@ -1,14 +1,12 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type LineChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
import { type TypedAggregateChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedAggregateChartConfiguration';
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
import { type TypedGaugeChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedGaugeChartConfiguration';
import { type TypedLineChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedLineChartConfiguration';
import { type TypedPieChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedPieChartConfiguration';
export type ChartWidgetConfiguration =
| BarChartConfiguration
| GaugeChartConfiguration
| LineChartConfiguration
| AggregateChartConfiguration
| PieChartConfiguration;
| TypedBarChartConfiguration
| TypedGaugeChartConfiguration
| TypedLineChartConfiguration
| TypedAggregateChartConfiguration
| TypedPieChartConfiguration;
@@ -0,0 +1,8 @@
export enum GraphType {
VERTICAL_BAR = 'VERTICAL_BAR',
HORIZONTAL_BAR = 'HORIZONTAL_BAR',
LINE = 'LINE',
PIE = 'PIE',
AGGREGATE = 'AGGREGATE',
GAUGE = 'GAUGE',
}
@@ -0,0 +1,8 @@
import {
type AggregateChartConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
export type TypedAggregateChartConfiguration = AggregateChartConfiguration & {
configurationType: WidgetConfigurationType.AGGREGATE_CHART;
};
@@ -0,0 +1,8 @@
import {
type BarChartConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
export type TypedBarChartConfiguration = BarChartConfiguration & {
configurationType: WidgetConfigurationType.BAR_CHART;
};
@@ -0,0 +1,8 @@
import {
type GaugeChartConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
export type TypedGaugeChartConfiguration = GaugeChartConfiguration & {
configurationType: WidgetConfigurationType.GAUGE_CHART;
};
@@ -0,0 +1,8 @@
import {
type LineChartConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
export type TypedLineChartConfiguration = LineChartConfiguration & {
configurationType: WidgetConfigurationType.LINE_CHART;
};
@@ -0,0 +1,8 @@
import {
type PieChartConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
export type TypedPieChartConfiguration = PieChartConfiguration & {
configurationType: WidgetConfigurationType.PIE_CHART;
};
@@ -0,0 +1,51 @@
import { assertChartWidgetOrThrow } from '@/command-menu/pages/page-layout/utils/assertChartWidgetOrThrow';
import {
ALL_CHART_CONFIGURATIONS,
createTestWidget,
TEST_BAR_CHART_CONFIGURATION,
TEST_IFRAME_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('assertChartWidgetOrThrow', () => {
it('does not throw for valid chart widget', () => {
const widget = createTestWidget({
configuration: TEST_BAR_CHART_CONFIGURATION,
});
expect(() => assertChartWidgetOrThrow(widget)).not.toThrow();
});
it('throws when objectMetadataId is missing', () => {
const widget = createTestWidget({ objectMetadataId: undefined });
expect(() => assertChartWidgetOrThrow(widget)).toThrow(
'Widget objectMetadataId is required',
);
});
it('throws when objectMetadataId is null', () => {
const widget = createTestWidget({ objectMetadataId: null });
expect(() => assertChartWidgetOrThrow(widget)).toThrow(
'Widget objectMetadataId is required',
);
});
it('throws when configuration is not a valid chart type', () => {
const widget = createTestWidget({
configuration: TEST_IFRAME_CONFIGURATION,
});
expect(() => assertChartWidgetOrThrow(widget)).toThrow(
'Expected chart configuration but got IframeConfiguration',
);
});
it('passes for all valid chart types', () => {
ALL_CHART_CONFIGURATIONS.forEach((config) => {
const widget = createTestWidget({ configuration: config });
expect(() => assertChartWidgetOrThrow(widget)).not.toThrow();
});
});
});
@@ -1,9 +1,10 @@
import { type TypedBarChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedBarChartConfiguration';
import { type TypedPieChartConfiguration } from '@/command-menu/pages/page-layout/types/TypedPieChartConfiguration';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import {
type BarChartConfiguration,
BarChartGroupMode,
GraphOrderBy,
type PieChartConfiguration,
WidgetConfigurationType,
} from '~/generated/graphql';
import { buildChartGroupByFieldConfigUpdate } from '@/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate';
@@ -12,7 +13,8 @@ describe('buildChartGroupByFieldConfigUpdate', () => {
const result = buildChartGroupByFieldConfigUpdate({
configuration: {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration,
configurationType: WidgetConfigurationType.BAR_CHART,
} as TypedBarChartConfiguration,
fieldMetadataIdKey: 'primaryAxisGroupByFieldMetadataId',
subFieldNameKey: 'primaryAxisGroupBySubFieldName',
fieldId: 'field',
@@ -29,7 +31,8 @@ describe('buildChartGroupByFieldConfigUpdate', () => {
const result = buildChartGroupByFieldConfigUpdate({
configuration: {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration,
configurationType: WidgetConfigurationType.BAR_CHART,
} as TypedBarChartConfiguration,
fieldMetadataIdKey: 'primaryAxisGroupByFieldMetadataId',
subFieldNameKey: 'primaryAxisGroupBySubFieldName',
fieldId: null,
@@ -46,7 +49,8 @@ describe('buildChartGroupByFieldConfigUpdate', () => {
const result = buildChartGroupByFieldConfigUpdate({
configuration: {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration,
configurationType: WidgetConfigurationType.BAR_CHART,
} as TypedBarChartConfiguration,
fieldMetadataIdKey: 'secondaryAxisGroupByFieldMetadataId',
subFieldNameKey: 'secondaryAxisGroupBySubFieldName',
fieldId: 'field',
@@ -62,7 +66,8 @@ describe('buildChartGroupByFieldConfigUpdate', () => {
const result = buildChartGroupByFieldConfigUpdate({
configuration: {
__typename: 'PieChartConfiguration',
} as PieChartConfiguration,
configurationType: WidgetConfigurationType.PIE_CHART,
} as TypedPieChartConfiguration,
fieldMetadataIdKey: 'groupByFieldMetadataId',
subFieldNameKey: 'groupBySubFieldName',
fieldId: 'field',
@@ -0,0 +1,122 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { convertAggregateOperationForDateField } from '@/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { FieldMetadataType } from 'twenty-shared/types';
import { TEST_BAR_CHART_CONFIGURATION } from '~/testing/mock-data/widget-configurations';
const createMockObjectMetadataItem = (
fields: { id: string; type: FieldMetadataType }[],
): ObjectMetadataItem =>
({
id: 'object-1',
fields: fields.map((f) => ({ ...f, name: 'field' })),
}) as ObjectMetadataItem;
describe('convertAggregateOperationForDateField', () => {
it('returns COUNT when date field uses MIN operation', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateFieldMetadataId: 'date-field',
aggregateOperation: AggregateOperations.MIN,
};
const objectMetadataItem = createMockObjectMetadataItem([
{ id: 'date-field', type: FieldMetadataType.DATE },
]);
const result = convertAggregateOperationForDateField(
config,
objectMetadataItem,
);
expect(result).toBe(AggregateOperations.COUNT);
});
it('returns COUNT when datetime field uses MAX operation', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateFieldMetadataId: 'datetime-field',
aggregateOperation: AggregateOperations.MAX,
};
const objectMetadataItem = createMockObjectMetadataItem([
{ id: 'datetime-field', type: FieldMetadataType.DATE_TIME },
]);
const result = convertAggregateOperationForDateField(
config,
objectMetadataItem,
);
expect(result).toBe(AggregateOperations.COUNT);
});
it('returns undefined when field is not a date type', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateFieldMetadataId: 'number-field',
aggregateOperation: AggregateOperations.MIN,
};
const objectMetadataItem = createMockObjectMetadataItem([
{ id: 'number-field', type: FieldMetadataType.NUMBER },
]);
const result = convertAggregateOperationForDateField(
config,
objectMetadataItem,
);
expect(result).toBeUndefined();
});
it('returns undefined when operation is not MIN or MAX', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateFieldMetadataId: 'date-field',
aggregateOperation: AggregateOperations.SUM,
};
const objectMetadataItem = createMockObjectMetadataItem([
{ id: 'date-field', type: FieldMetadataType.DATE },
]);
const result = convertAggregateOperationForDateField(
config,
objectMetadataItem,
);
expect(result).toBeUndefined();
});
it('returns undefined when objectMetadataItem is undefined', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateOperation: AggregateOperations.MIN,
};
const result = convertAggregateOperationForDateField(config, undefined);
expect(result).toBeUndefined();
});
it('returns undefined when field is not found', () => {
const config: ChartConfiguration = {
...TEST_BAR_CHART_CONFIGURATION,
aggregateFieldMetadataId: 'missing-field',
aggregateOperation: AggregateOperations.MIN,
};
const objectMetadataItem = createMockObjectMetadataItem([
{ id: 'other-field', type: FieldMetadataType.DATE },
]);
const result = convertAggregateOperationForDateField(
config,
objectMetadataItem,
);
expect(result).toBeUndefined();
});
});
@@ -0,0 +1,47 @@
import { convertBarOrLineChartConfigToPieChart } from '@/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart';
import { type BarChartConfiguration } from '~/generated/graphql';
import {
TEST_BAR_CHART_CONFIGURATION,
TEST_LINE_CHART_CONFIGURATION,
TEST_PIE_CHART_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('convertBarOrLineChartConfigToPieChart', () => {
it('maps bar chart fields to pie chart equivalents', () => {
const result = convertBarOrLineChartConfigToPieChart(
TEST_BAR_CHART_CONFIGURATION,
);
expect(result).toEqual({
groupByFieldMetadataId:
TEST_BAR_CHART_CONFIGURATION.primaryAxisGroupByFieldMetadataId,
groupBySubFieldName:
TEST_BAR_CHART_CONFIGURATION.primaryAxisGroupBySubFieldName,
dateGranularity: TEST_BAR_CHART_CONFIGURATION.primaryAxisDateGranularity,
orderBy: TEST_BAR_CHART_CONFIGURATION.primaryAxisOrderBy,
});
});
it('maps line chart fields to pie chart equivalents', () => {
const result = convertBarOrLineChartConfigToPieChart(
TEST_LINE_CHART_CONFIGURATION,
);
expect(result).toEqual({
groupByFieldMetadataId:
TEST_LINE_CHART_CONFIGURATION.primaryAxisGroupByFieldMetadataId,
groupBySubFieldName:
TEST_LINE_CHART_CONFIGURATION.primaryAxisGroupBySubFieldName,
dateGranularity: TEST_LINE_CHART_CONFIGURATION.primaryAxisDateGranularity,
orderBy: TEST_LINE_CHART_CONFIGURATION.primaryAxisOrderBy,
});
});
it('returns empty object for non-bar/line chart configuration', () => {
const result = convertBarOrLineChartConfigToPieChart(
TEST_PIE_CHART_CONFIGURATION as unknown as BarChartConfiguration,
);
expect(result).toEqual({});
});
});
@@ -0,0 +1,50 @@
import { convertPieChartConfigToBarOrLineChart } from '@/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart';
import { type PieChartConfiguration } from '~/generated/graphql';
import {
TEST_BAR_CHART_CONFIGURATION,
TEST_PIE_CHART_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('convertPieChartConfigToBarOrLineChart', () => {
it('maps pie chart fields to bar/line chart equivalents', () => {
const result = convertPieChartConfigToBarOrLineChart(
TEST_PIE_CHART_CONFIGURATION,
);
expect(result).toEqual({
primaryAxisGroupByFieldMetadataId:
TEST_PIE_CHART_CONFIGURATION.groupByFieldMetadataId,
primaryAxisGroupBySubFieldName:
TEST_PIE_CHART_CONFIGURATION.groupBySubFieldName,
primaryAxisDateGranularity: TEST_PIE_CHART_CONFIGURATION.dateGranularity,
primaryAxisOrderBy: TEST_PIE_CHART_CONFIGURATION.orderBy,
});
});
it('handles null/undefined fields', () => {
const minimalPieConfig: PieChartConfiguration = {
...TEST_PIE_CHART_CONFIGURATION,
groupByFieldMetadataId: undefined as unknown as string,
groupBySubFieldName: null,
dateGranularity: null,
orderBy: null,
};
const result = convertPieChartConfigToBarOrLineChart(minimalPieConfig);
expect(result).toEqual({
primaryAxisGroupByFieldMetadataId: undefined,
primaryAxisGroupBySubFieldName: null,
primaryAxisDateGranularity: null,
primaryAxisOrderBy: null,
});
});
it('returns empty object for non-pie chart configuration', () => {
const result = convertPieChartConfigToBarOrLineChart(
TEST_BAR_CHART_CONFIGURATION as unknown as PieChartConfiguration,
);
expect(result).toEqual({});
});
});
@@ -17,14 +17,14 @@ import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/se
import { SHOW_LEGEND_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ShowLegendSetting';
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting';
import { IconAxisX, IconAxisY } from 'twenty-ui/display';
import { GraphType } from '~/generated-metadata/graphql';
import { getBarChartSettings } from '@/command-menu/pages/page-layout/utils/getBarChartSettings';
import { IconAxisX, IconAxisY } from 'twenty-ui/display';
import { BarChartLayout } from '~/generated/graphql';
describe('getBarChartSettings', () => {
describe('Vertical bar chart', () => {
it('should place primary axis items under "X axis" heading', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
const result = getBarChartSettings(BarChartLayout.VERTICAL);
const xAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.X_AXIS.id,
@@ -41,7 +41,7 @@ describe('getBarChartSettings', () => {
});
it('should place secondary axis items under "Y axis" heading', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
const result = getBarChartSettings(BarChartLayout.VERTICAL);
const yAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.Y_AXIS.id,
@@ -61,7 +61,7 @@ describe('getBarChartSettings', () => {
});
it('should have all expected groups in correct order', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
const result = getBarChartSettings(BarChartLayout.VERTICAL);
expect(result).toHaveLength(4);
expect(result[0].heading).toBe(CHART_SETTINGS_HEADINGS.DATA);
@@ -73,7 +73,7 @@ describe('getBarChartSettings', () => {
describe('Horizontal bar chart', () => {
it('should place SECONDARY axis items under "X axis" heading', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const result = getBarChartSettings(BarChartLayout.HORIZONTAL);
const xAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.X_AXIS.id,
@@ -93,7 +93,7 @@ describe('getBarChartSettings', () => {
});
it('should place PRIMARY axis items under "Y axis" heading', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const result = getBarChartSettings(BarChartLayout.HORIZONTAL);
const yAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.Y_AXIS.id,
@@ -110,7 +110,7 @@ describe('getBarChartSettings', () => {
});
it('should have all expected groups in correct order', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const result = getBarChartSettings(BarChartLayout.HORIZONTAL);
expect(result).toHaveLength(4);
expect(result[0].heading).toBe(CHART_SETTINGS_HEADINGS.DATA);
@@ -122,8 +122,8 @@ describe('getBarChartSettings', () => {
describe('Common groups', () => {
it('should have consistent Data group for both orientations', () => {
const verticalResult = getBarChartSettings(GraphType.VERTICAL_BAR);
const horizontalResult = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const verticalResult = getBarChartSettings(BarChartLayout.VERTICAL);
const horizontalResult = getBarChartSettings(BarChartLayout.HORIZONTAL);
const verticalDataGroup = verticalResult.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.DATA.id,
@@ -140,8 +140,8 @@ describe('getBarChartSettings', () => {
});
it('should have consistent Style group for both orientations', () => {
const verticalResult = getBarChartSettings(GraphType.VERTICAL_BAR);
const horizontalResult = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const verticalResult = getBarChartSettings(BarChartLayout.VERTICAL);
const horizontalResult = getBarChartSettings(BarChartLayout.HORIZONTAL);
const verticalStyleGroup = verticalResult.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.STYLE.id,
@@ -0,0 +1,67 @@
import { getChartLimitMessage } from '@/command-menu/pages/page-layout/utils/getChartLimitMessage';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { WidgetConfigurationType } from '~/generated/graphql';
describe('getChartLimitMessage', () => {
it('returns date-based message for bar chart with date axis', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.BAR_CHART,
isPrimaryAxisDate: true,
primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY,
});
expect(result).toContain('Undisplayed data');
expect(result).toContain('days');
expect(result).toContain('per chart');
});
it('returns bars message for bar chart without date axis', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.BAR_CHART,
isPrimaryAxisDate: false,
primaryAxisDateGranularity: null,
});
expect(result).toContain('bars per chart');
});
it('returns data points message for line chart without date axis', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.LINE_CHART,
isPrimaryAxisDate: false,
primaryAxisDateGranularity: null,
});
expect(result).toContain('data points per chart');
});
it('returns slices message for pie chart', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.PIE_CHART,
isPrimaryAxisDate: false,
primaryAxisDateGranularity: null,
});
expect(result).toContain('slices per chart');
});
it('returns date-based message with weeks granularity', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.LINE_CHART,
isPrimaryAxisDate: true,
primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.WEEK,
});
expect(result).toContain('weeks');
});
it('returns slices message when isPrimaryAxisDate is true but granularity is null', () => {
const result = getChartLimitMessage({
widgetConfigurationType: WidgetConfigurationType.PIE_CHART,
isPrimaryAxisDate: true,
primaryAxisDateGranularity: null,
});
expect(result).toContain('slices per chart');
});
});
@@ -0,0 +1,46 @@
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { getConfigurationTypeFromGraphType } from '@/command-menu/pages/page-layout/utils/getConfigurationTypeFromGraphType';
import { WidgetConfigurationType } from '~/generated/graphql';
describe('getConfigurationTypeFromGraphType', () => {
it('returns BAR_CHART for VERTICAL_BAR', () => {
expect(getConfigurationTypeFromGraphType(GraphType.VERTICAL_BAR)).toBe(
WidgetConfigurationType.BAR_CHART,
);
});
it('returns BAR_CHART for HORIZONTAL_BAR', () => {
expect(getConfigurationTypeFromGraphType(GraphType.HORIZONTAL_BAR)).toBe(
WidgetConfigurationType.BAR_CHART,
);
});
it('returns LINE_CHART for LINE', () => {
expect(getConfigurationTypeFromGraphType(GraphType.LINE)).toBe(
WidgetConfigurationType.LINE_CHART,
);
});
it('returns PIE_CHART for PIE', () => {
expect(getConfigurationTypeFromGraphType(GraphType.PIE)).toBe(
WidgetConfigurationType.PIE_CHART,
);
});
it('returns AGGREGATE_CHART for AGGREGATE', () => {
expect(getConfigurationTypeFromGraphType(GraphType.AGGREGATE)).toBe(
WidgetConfigurationType.AGGREGATE_CHART,
);
});
it('returns GAUGE_CHART for GAUGE', () => {
expect(getConfigurationTypeFromGraphType(GraphType.GAUGE)).toBe(
WidgetConfigurationType.GAUGE_CHART,
);
});
it('throws for unknown graph type', () => {
const unknownGraphType = 'UNKNOWN' as never;
expect(() => getConfigurationTypeFromGraphType(unknownGraphType)).toThrow();
});
});
@@ -0,0 +1,71 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { getCurrentGraphTypeFromConfig } from '@/command-menu/pages/page-layout/utils/getCurrentGraphTypeFromConfig';
import {
TEST_AGGREGATE_CHART_CONFIGURATION,
TEST_BAR_CHART_CONFIGURATION,
TEST_BAR_CHART_CONFIGURATION_HORIZONTAL,
TEST_GAUGE_CHART_CONFIGURATION,
TEST_LINE_CHART_CONFIGURATION,
TEST_PIE_CHART_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('getCurrentGraphTypeFromConfig', () => {
it('returns VERTICAL_BAR for bar chart with vertical layout', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_BAR_CHART_CONFIGURATION as ChartConfiguration,
),
).toBe(GraphType.VERTICAL_BAR);
});
it('returns HORIZONTAL_BAR for bar chart with horizontal layout', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_BAR_CHART_CONFIGURATION_HORIZONTAL as ChartConfiguration,
),
).toBe(GraphType.HORIZONTAL_BAR);
});
it('returns LINE for line chart', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_LINE_CHART_CONFIGURATION as ChartConfiguration,
),
).toBe(GraphType.LINE);
});
it('returns PIE for pie chart', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_PIE_CHART_CONFIGURATION as ChartConfiguration,
),
).toBe(GraphType.PIE);
});
it('returns AGGREGATE for aggregate chart', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_AGGREGATE_CHART_CONFIGURATION as ChartConfiguration,
),
).toBe(GraphType.AGGREGATE);
});
it('returns GAUGE for gauge chart', () => {
expect(
getCurrentGraphTypeFromConfig(
TEST_GAUGE_CHART_CONFIGURATION as ChartConfiguration,
),
).toBe(GraphType.GAUGE);
});
it('throws for unknown configuration type', () => {
const unknownConfig = {
__typename: 'UnknownConfiguration',
} as unknown as ChartConfiguration;
expect(() => getCurrentGraphTypeFromConfig(unknownConfig)).toThrow(
'Unknown chart configuration type',
);
});
});
@@ -0,0 +1,64 @@
import { getDateGranularityPluralLabel } from '@/command-menu/pages/page-layout/utils/getDateGranularityPluralLabel';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
describe('getDateGranularityPluralLabel', () => {
it('returns "days" for DAY granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.DAY),
).toBe('days');
});
it('returns "weeks" for WEEK granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.WEEK),
).toBe('weeks');
});
it('returns "months" for MONTH granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.MONTH),
).toBe('months');
});
it('returns "quarters" for QUARTER granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.QUARTER),
).toBe('quarters');
});
it('returns "years" for YEAR granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.YEAR),
).toBe('years');
});
it('returns "days" for DAY_OF_THE_WEEK granularity', () => {
expect(
getDateGranularityPluralLabel(
ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK,
),
).toBe('days');
});
it('returns "months" for MONTH_OF_THE_YEAR granularity', () => {
expect(
getDateGranularityPluralLabel(
ObjectRecordGroupByDateGranularity.MONTH_OF_THE_YEAR,
),
).toBe('months');
});
it('returns "quarters" for QUARTER_OF_THE_YEAR granularity', () => {
expect(
getDateGranularityPluralLabel(
ObjectRecordGroupByDateGranularity.QUARTER_OF_THE_YEAR,
),
).toBe('quarters');
});
it('returns "items" for NONE granularity', () => {
expect(
getDateGranularityPluralLabel(ObjectRecordGroupByDateGranularity.NONE),
).toBe('items');
});
});
@@ -1,31 +0,0 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
} from '~/generated/graphql';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
describe('isAggregateChartConfiguration', () => {
it('should return true for AggregateChartConfiguration', () => {
const configuration = {
__typename: 'AggregateChartConfiguration',
} as AggregateChartConfiguration;
expect(isAggregateChartConfiguration(configuration)).toBe(true);
});
it('should return false for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isAggregateChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isAggregateChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isAggregateChartConfiguration(undefined)).toBe(false);
});
});
@@ -1,31 +0,0 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
import { isBarChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarChartConfiguration';
describe('isBarChartConfiguration', () => {
it('should return true for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration' as const,
} as BarChartConfiguration;
expect(isBarChartConfiguration(configuration)).toBe(true);
});
it('should return false for LineChartConfiguration', () => {
const configuration = {
__typename: 'LineChartConfiguration' as const,
} as LineChartConfiguration;
expect(isBarChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isBarChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isBarChartConfiguration(undefined)).toBe(false);
});
});
@@ -1,40 +0,0 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
describe('isBarOrLineChartConfiguration', () => {
it('should return true for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isBarOrLineChartConfiguration(configuration)).toBe(true);
});
it('should return true for LineChartConfiguration', () => {
const configuration = {
__typename: 'LineChartConfiguration',
} as LineChartConfiguration;
expect(isBarOrLineChartConfiguration(configuration)).toBe(true);
});
it('should return false for PieChartConfiguration', () => {
const configuration = {
__typename: 'PieChartConfiguration',
} as PieChartConfiguration;
expect(isBarOrLineChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isBarOrLineChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isBarOrLineChartConfiguration(undefined)).toBe(false);
});
});
@@ -1,76 +0,0 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type IframeConfiguration,
type LineChartConfiguration,
type PieChartConfiguration,
type StandaloneRichTextConfiguration,
} from '~/generated/graphql';
import { isChartConfiguration } from '@/command-menu/pages/page-layout/utils/isChartConfiguration';
describe('isChartConfiguration', () => {
it('should return true for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isChartConfiguration(configuration)).toBe(true);
});
it('should return true for LineChartConfiguration', () => {
const configuration = {
__typename: 'LineChartConfiguration',
} as LineChartConfiguration;
expect(isChartConfiguration(configuration)).toBe(true);
});
it('should return true for PieChartConfiguration', () => {
const configuration = {
__typename: 'PieChartConfiguration',
} as PieChartConfiguration;
expect(isChartConfiguration(configuration)).toBe(true);
});
it('should return true for AggregateChartConfiguration', () => {
const configuration = {
__typename: 'AggregateChartConfiguration',
} as AggregateChartConfiguration;
expect(isChartConfiguration(configuration)).toBe(true);
});
it('should return true for GaugeChartConfiguration', () => {
const configuration = {
__typename: 'GaugeChartConfiguration',
} as GaugeChartConfiguration;
expect(isChartConfiguration(configuration)).toBe(true);
});
it('should return false for IframeConfiguration', () => {
const configuration = {
__typename: 'IframeConfiguration',
} as IframeConfiguration;
expect(isChartConfiguration(configuration)).toBe(false);
});
it('should return false for StandaloneRichTextConfiguration', () => {
const configuration = {
__typename: 'StandaloneRichTextConfiguration',
} as StandaloneRichTextConfiguration;
expect(isChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isChartConfiguration(undefined)).toBe(false);
});
});
@@ -0,0 +1,51 @@
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
import {
createTestWidget,
TEST_BAR_CHART_CONFIGURATION,
TEST_FIELDS_CONFIGURATION,
TEST_IFRAME_CONFIGURATION,
TEST_LINE_CHART_CONFIGURATION,
TEST_PIE_CHART_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('isChartWidget', () => {
it('returns true for widget with BarChartConfiguration', () => {
const widget = createTestWidget({
configuration: TEST_BAR_CHART_CONFIGURATION,
});
expect(isChartWidget(widget)).toBe(true);
});
it('returns true for widget with LineChartConfiguration', () => {
const widget = createTestWidget({
configuration: TEST_LINE_CHART_CONFIGURATION,
});
expect(isChartWidget(widget)).toBe(true);
});
it('returns true for widget with PieChartConfiguration', () => {
const widget = createTestWidget({
configuration: TEST_PIE_CHART_CONFIGURATION,
});
expect(isChartWidget(widget)).toBe(true);
});
it('returns false for widget with IframeConfiguration', () => {
const widget = createTestWidget({
configuration: TEST_IFRAME_CONFIGURATION,
});
expect(isChartWidget(widget)).toBe(false);
});
it('returns false for widget with FieldsConfiguration', () => {
const widget = createTestWidget({
configuration: TEST_FIELDS_CONFIGURATION,
});
expect(isChartWidget(widget)).toBe(false);
});
});
@@ -1,31 +0,0 @@
import {
type BarChartConfiguration,
type GaugeChartConfiguration,
} from '~/generated/graphql';
import { isGaugeChartConfiguration } from '@/command-menu/pages/page-layout/utils/isGaugeChartConfiguration';
describe('isGaugeChartConfiguration', () => {
it('should return true for GaugeChartConfiguration', () => {
const configuration = {
__typename: 'GaugeChartConfiguration',
} as GaugeChartConfiguration;
expect(isGaugeChartConfiguration(configuration)).toBe(true);
});
it('should return false for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isGaugeChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isGaugeChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isGaugeChartConfiguration(undefined)).toBe(false);
});
});
@@ -1,31 +0,0 @@
import {
type BarChartConfiguration,
type IframeConfiguration,
} from '~/generated/graphql';
import { isIframeConfiguration } from '@/command-menu/pages/page-layout/utils/isIframeConfiguration';
describe('isIframeConfiguration', () => {
it('should return true for IframeConfiguration', () => {
const configuration = {
__typename: 'IframeConfiguration',
} as IframeConfiguration;
expect(isIframeConfiguration(configuration)).toBe(true);
});
it('should return false for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isIframeConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isIframeConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isIframeConfiguration(undefined)).toBe(false);
});
});
@@ -1,31 +0,0 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
import { isLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isLineChartConfiguration';
describe('isLineChartConfiguration', () => {
it('should return true for LineChartConfiguration', () => {
const configuration = {
__typename: 'LineChartConfiguration',
} as LineChartConfiguration;
expect(isLineChartConfiguration(configuration)).toBe(true);
});
it('should return false for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isLineChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isLineChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isLineChartConfiguration(undefined)).toBe(false);
});
});
@@ -1,12 +1,13 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { isMinMaxRangeValid } from '@/command-menu/pages/page-layout/utils/isMinMaxRangeValid';
import { GraphType } from '~/generated/graphql';
import { BarChartLayout, WidgetConfigurationType } from '~/generated/graphql';
describe('isMinMaxRangeValid', () => {
const mockConfiguration = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMin: 10,
rangeMax: 100,
} as ChartConfiguration;
@@ -78,7 +79,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when new rangeMin is negative and greater than existing rangeMax', () => {
const configWithNegatives = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMin: -100,
rangeMax: -10,
} as ChartConfiguration;
@@ -101,7 +103,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when new rangeMin is zero and greater than existing rangeMax', () => {
const configWithZero = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMin: 0,
rangeMax: 100,
} as ChartConfiguration;
@@ -119,7 +122,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when setting rangeMin on configuration without any range values', () => {
const emptyConfig = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
} as ChartConfiguration;
const result = isMinMaxRangeValid(
@@ -134,7 +138,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when setting rangeMax on configuration without any range values', () => {
const emptyConfig = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
} as ChartConfiguration;
const result = isMinMaxRangeValid(
@@ -149,7 +154,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when setting rangeMin without existing rangeMax', () => {
const configWithOnlyMin = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMin: 10,
} as ChartConfiguration;
@@ -165,7 +171,8 @@ describe('isMinMaxRangeValid', () => {
it('should be valid when setting rangeMax without existing rangeMin', () => {
const configWithOnlyMax = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMax: 100,
} as ChartConfiguration;
@@ -181,7 +188,8 @@ describe('isMinMaxRangeValid', () => {
it('should be invalid when setting rangeMin that would exceed existing rangeMax', () => {
const configWithOnlyMax = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMax: 100,
} as ChartConfiguration;
@@ -197,7 +205,8 @@ describe('isMinMaxRangeValid', () => {
it('should be invalid when setting rangeMax that would be less than existing rangeMin', () => {
const configWithOnlyMin = {
__typename: 'BarChartConfiguration',
graphType: GraphType.VERTICAL_BAR,
configurationType: WidgetConfigurationType.BAR_CHART,
layout: BarChartLayout.VERTICAL,
rangeMin: 50,
} as ChartConfiguration;
@@ -1,31 +0,0 @@
import {
type BarChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
describe('isPieChartConfiguration', () => {
it('should return true for PieChartConfiguration', () => {
const configuration = {
__typename: 'PieChartConfiguration',
} as PieChartConfiguration;
expect(isPieChartConfiguration(configuration)).toBe(true);
});
it('should return false for BarChartConfiguration', () => {
const configuration = {
__typename: 'BarChartConfiguration',
} as BarChartConfiguration;
expect(isPieChartConfiguration(configuration)).toBe(false);
});
it('should return false for null', () => {
expect(isPieChartConfiguration(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isPieChartConfiguration(undefined)).toBe(false);
});
});
@@ -0,0 +1,75 @@
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import {
ALL_CHART_CONFIGURATIONS,
TEST_BAR_CHART_CONFIGURATION,
TEST_IFRAME_CONFIGURATION,
TEST_PIE_CHART_CONFIGURATION,
TEST_STANDALONE_RICH_TEXT_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('isWidgetConfigurationOfType', () => {
it('returns true when __typename matches', () => {
expect(
isWidgetConfigurationOfType(
TEST_BAR_CHART_CONFIGURATION,
'BarChartConfiguration',
),
).toBe(true);
});
it('returns false when __typename does not match', () => {
expect(
isWidgetConfigurationOfType(
TEST_BAR_CHART_CONFIGURATION,
'PieChartConfiguration',
),
).toBe(false);
});
it('returns false for null configuration', () => {
expect(isWidgetConfigurationOfType(null, 'BarChartConfiguration')).toBe(
false,
);
});
it('returns false for undefined configuration', () => {
expect(
isWidgetConfigurationOfType(undefined, 'BarChartConfiguration'),
).toBe(false);
});
it('correctly identifies PieChartConfiguration', () => {
expect(
isWidgetConfigurationOfType(
TEST_PIE_CHART_CONFIGURATION,
'PieChartConfiguration',
),
).toBe(true);
});
it('correctly identifies IframeConfiguration', () => {
expect(
isWidgetConfigurationOfType(
TEST_IFRAME_CONFIGURATION,
'IframeConfiguration',
),
).toBe(true);
});
it('correctly identifies StandaloneRichTextConfiguration', () => {
expect(
isWidgetConfigurationOfType(
TEST_STANDALONE_RICH_TEXT_CONFIGURATION,
'StandaloneRichTextConfiguration',
),
).toBe(true);
});
it('works with all chart configuration types', () => {
ALL_CHART_CONFIGURATIONS.forEach((config) => {
expect(isWidgetConfigurationOfType(config, config.__typename!)).toBe(
true,
);
});
});
});
@@ -0,0 +1,41 @@
import { isWidgetConfigurationOfTypeGraph } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfTypeGraph';
import {
ALL_CHART_CONFIGURATIONS,
TEST_FIELDS_CONFIGURATION,
TEST_IFRAME_CONFIGURATION,
TEST_STANDALONE_RICH_TEXT_CONFIGURATION,
} from '~/testing/mock-data/widget-configurations';
describe('isWidgetConfigurationOfTypeGraph', () => {
it('returns true for all chart configurations', () => {
ALL_CHART_CONFIGURATIONS.forEach((config) => {
expect(isWidgetConfigurationOfTypeGraph(config)).toBe(true);
});
});
it('returns false for IframeConfiguration', () => {
expect(isWidgetConfigurationOfTypeGraph(TEST_IFRAME_CONFIGURATION)).toBe(
false,
);
});
it('returns false for StandaloneRichTextConfiguration', () => {
expect(
isWidgetConfigurationOfTypeGraph(TEST_STANDALONE_RICH_TEXT_CONFIGURATION),
).toBe(false);
});
it('returns false for FieldsConfiguration', () => {
expect(isWidgetConfigurationOfTypeGraph(TEST_FIELDS_CONFIGURATION)).toBe(
false,
);
});
it('returns false for null', () => {
expect(isWidgetConfigurationOfTypeGraph(null)).toBe(false);
});
it('returns false for undefined', () => {
expect(isWidgetConfigurationOfTypeGraph(undefined)).toBe(false);
});
});
@@ -1,8 +1,6 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { isBarChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarChartConfiguration';
import { isFieldOrRelationNestedFieldDateKind } from '@/command-menu/pages/page-layout/utils/isFieldOrNestedFieldDateKind';
import { isLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
@@ -40,9 +38,18 @@ export const buildChartGroupByFieldConfigUpdate = <
[subFieldNameKey]: subFieldName,
};
const isBarChart = isBarChartConfiguration(configuration);
const isLineChart = isLineChartConfiguration(configuration);
const isPieChart = isPieChartConfiguration(configuration);
const isBarChart = isWidgetConfigurationOfType(
configuration,
'BarChartConfiguration',
);
const isLineChart = isWidgetConfigurationOfType(
configuration,
'LineChartConfiguration',
);
const isPieChart = isWidgetConfigurationOfType(
configuration,
'PieChartConfiguration',
);
if (isPrimaryAxis) {
const existingOrderBy =
@@ -1,11 +1,11 @@
import { type BarLineChartConvertibleFields } from '@/command-menu/pages/page-layout/types/BarLineChartConvertibleFields';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type PieChartConfiguration } from '~/generated/graphql';
export const convertPieChartConfigToBarOrLineChart = (
configuration: PieChartConfiguration,
): BarLineChartConvertibleFields => {
if (!isPieChartConfiguration(configuration)) {
if (!isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
return {};
}
@@ -19,12 +19,12 @@ import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout
import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
import { IconAxisX, IconAxisY } from 'twenty-ui/display';
import { GraphType } from '~/generated-metadata/graphql';
import { BarChartLayout } from '~/generated/graphql';
export const getBarChartSettings = (
graphType: GraphType.VERTICAL_BAR | GraphType.HORIZONTAL_BAR,
layout: BarChartLayout,
): ChartSettingsGroup[] => {
const isHorizontal = graphType === GraphType.HORIZONTAL_BAR;
const isHorizontal = layout === BarChartLayout.HORIZONTAL;
const dataDisplayXIcon = isHorizontal ? IconAxisY : IconAxisX;
const dataDisplayYIcon = isHorizontal ? IconAxisX : IconAxisY;
@@ -5,11 +5,10 @@ import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/
import { t } from '@lingui/core/macro';
import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { GraphType } from '~/generated/graphql';
import { WidgetConfigurationType } from '~/generated/graphql';
type GetChartLimitMessageParams = {
graphType: GraphType;
widgetConfigurationType: WidgetConfigurationType;
isPrimaryAxisDate: boolean;
primaryAxisDateGranularity:
| ObjectRecordGroupByDateGranularity
@@ -18,15 +17,14 @@ type GetChartLimitMessageParams = {
};
export const getChartLimitMessage = ({
graphType,
widgetConfigurationType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
}: GetChartLimitMessageParams): string => {
const maxItems =
graphType === GraphType.LINE
widgetConfigurationType === WidgetConfigurationType.LINE_CHART
? LINE_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_DATA_POINTS
: graphType === GraphType.VERTICAL_BAR ||
graphType === GraphType.HORIZONTAL_BAR
: widgetConfigurationType === WidgetConfigurationType.BAR_CHART
? BAR_CHART_CONSTANTS.MAXIMUM_NUMBER_OF_BARS
: PIE_CHART_MAXIMUM_NUMBER_OF_SLICES;
@@ -37,14 +35,11 @@ export const getChartLimitMessage = ({
return t`Undisplayed data: max ${maxItems} ${granularityLabel} per chart.`;
}
if (graphType === GraphType.LINE) {
if (widgetConfigurationType === WidgetConfigurationType.LINE_CHART) {
return t`Undisplayed data: max ${maxItems} data points per chart.`;
}
if (
graphType === GraphType.VERTICAL_BAR ||
graphType === GraphType.HORIZONTAL_BAR
) {
if (widgetConfigurationType === WidgetConfigurationType.BAR_CHART) {
return t`Undisplayed data: max ${maxItems} bars per chart.`;
}
@@ -0,0 +1,23 @@
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { assertUnreachable } from 'twenty-shared/utils';
import { WidgetConfigurationType } from '~/generated/graphql';
export const getConfigurationTypeFromGraphType = (
graphType: GraphType,
): WidgetConfigurationType => {
switch (graphType) {
case GraphType.VERTICAL_BAR:
case GraphType.HORIZONTAL_BAR:
return WidgetConfigurationType.BAR_CHART;
case GraphType.LINE:
return WidgetConfigurationType.LINE_CHART;
case GraphType.PIE:
return WidgetConfigurationType.PIE_CHART;
case GraphType.AGGREGATE:
return WidgetConfigurationType.AGGREGATE_CHART;
case GraphType.GAUGE:
return WidgetConfigurationType.GAUGE_CHART;
default:
assertUnreachable(graphType);
}
};
@@ -0,0 +1,35 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { GraphType } from '@/command-menu/pages/page-layout/types/GraphType';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { t } from '@lingui/core/macro';
import { BarChartLayout } from '~/generated/graphql';
export const getCurrentGraphTypeFromConfig = (
configuration: ChartConfiguration,
): GraphType => {
if (isWidgetConfigurationOfType(configuration, 'BarChartConfiguration')) {
return configuration.layout === BarChartLayout.HORIZONTAL
? GraphType.HORIZONTAL_BAR
: GraphType.VERTICAL_BAR;
}
if (isWidgetConfigurationOfType(configuration, 'LineChartConfiguration')) {
return GraphType.LINE;
}
if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
return GraphType.PIE;
}
if (
isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration')
) {
return GraphType.AGGREGATE;
}
if (isWidgetConfigurationOfType(configuration, 'GaugeChartConfiguration')) {
return GraphType.GAUGE;
}
throw new Error(t`Unknown chart configuration type`);
};
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type AggregateChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isAggregateChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is AggregateChartConfiguration => {
return configuration?.__typename === 'AggregateChartConfiguration';
};
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type BarChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isBarChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is BarChartConfiguration => {
return configuration?.__typename === 'BarChartConfiguration';
};
@@ -1,24 +0,0 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import { isBarChartConfiguration } from './isBarChartConfiguration';
import { isLineChartConfiguration } from './isLineChartConfiguration';
export const isBarOrLineChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is BarChartConfiguration | LineChartConfiguration => {
return (
isBarChartConfiguration(configuration) ||
isLineChartConfiguration(configuration)
);
};
@@ -1,27 +0,0 @@
import { type WidgetConfiguration } from '~/generated/graphql';
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { isAggregateChartConfiguration } from '@/command-menu/pages/page-layout/utils/isAggregateChartConfiguration';
import { isBarChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarChartConfiguration';
import { isGaugeChartConfiguration } from '@/command-menu/pages/page-layout/utils/isGaugeChartConfiguration';
import { isLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
export const isChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is ChartConfiguration => {
return (
isBarChartConfiguration(configuration) ||
isLineChartConfiguration(configuration) ||
isPieChartConfiguration(configuration) ||
isAggregateChartConfiguration(configuration) ||
isGaugeChartConfiguration(configuration)
);
};
@@ -1,8 +1,13 @@
import { type ChartWidget } from '@/command-menu/pages/page-layout/types/ChartWidget';
import { WidgetType } from '~/generated/graphql';
import { isWidgetConfigurationOfTypeGraph } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfTypeGraph';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { type WidgetConfiguration } from '~/generated/graphql';
export const isChartWidget = (
pageLayoutWidget: any,
pageLayoutWidget: PageLayoutWidget,
): pageLayoutWidget is ChartWidget => {
return pageLayoutWidget.type === WidgetType.GRAPH;
return isWidgetConfigurationOfTypeGraph(
// TODO: Remove this cast when we FieldsConfiguration and FieldConfiguration are in the backend
pageLayoutWidget.configuration as WidgetConfiguration,
);
};
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type GaugeChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isGaugeChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is GaugeChartConfiguration => {
return configuration?.__typename === 'GaugeChartConfiguration';
};
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type IframeConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isIframeConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is IframeConfiguration => {
return configuration?.__typename === 'IframeConfiguration';
};
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type LineChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isLineChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is LineChartConfiguration => {
return configuration?.__typename === 'LineChartConfiguration';
};
@@ -1,6 +1,6 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { isDefined } from 'twenty-shared/utils';
export const isMinMaxRangeValid = (
@@ -10,7 +10,11 @@ export const isMinMaxRangeValid = (
newValue: number,
configuration: ChartConfiguration,
): boolean => {
if (!isBarOrLineChartConfiguration(configuration)) {
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (!isBarOrLineChart) {
return true;
}
@@ -1,17 +0,0 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type PieChartConfiguration,
type WidgetConfiguration,
} from '~/generated/graphql';
export const isPieChartConfiguration = (
configuration:
| WidgetConfiguration
| FieldConfiguration
| FieldsConfiguration
| null
| undefined,
): configuration is PieChartConfiguration => {
return configuration?.__typename === 'PieChartConfiguration';
};
@@ -0,0 +1,65 @@
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type IframeConfiguration,
type LineChartConfiguration,
type PieChartConfiguration,
type StandaloneRichTextConfiguration,
type WidgetConfiguration,
type WidgetConfigurationType,
} from '~/generated/graphql';
type WidgetConfigurationTypenameMap = {
AggregateChartConfiguration: Omit<
AggregateChartConfiguration,
'configurationType'
> & {
configurationType: WidgetConfigurationType.AGGREGATE_CHART;
};
BarChartConfiguration: Omit<BarChartConfiguration, 'configurationType'> & {
configurationType: WidgetConfigurationType.BAR_CHART;
};
GaugeChartConfiguration: Omit<
GaugeChartConfiguration,
'configurationType'
> & {
configurationType: WidgetConfigurationType.GAUGE_CHART;
};
IframeConfiguration: Omit<IframeConfiguration, 'configurationType'> & {
configurationType: WidgetConfigurationType.IFRAME;
};
LineChartConfiguration: Omit<LineChartConfiguration, 'configurationType'> & {
configurationType: WidgetConfigurationType.LINE_CHART;
};
PieChartConfiguration: Omit<PieChartConfiguration, 'configurationType'> & {
configurationType: WidgetConfigurationType.PIE_CHART;
};
StandaloneRichTextConfiguration: Omit<
StandaloneRichTextConfiguration,
'configurationType'
> & {
configurationType: WidgetConfigurationType.STANDALONE_RICH_TEXT;
};
};
type WidgetConfigurationTypename = keyof WidgetConfigurationTypenameMap;
export type WidgetConfigurationOfType<T extends WidgetConfigurationTypename> =
WidgetConfigurationTypenameMap[T];
export const isWidgetConfigurationOfType = <
T extends WidgetConfigurationTypename,
>(
configuration:
| WidgetConfiguration
| FieldsConfiguration
| FieldConfiguration
| null
| undefined,
typename: T,
): configuration is WidgetConfigurationTypenameMap[T] => {
return configuration?.__typename === typename;
};
@@ -0,0 +1,22 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
import { type WidgetConfiguration } from '~/generated/graphql';
export const isWidgetConfigurationOfTypeGraph = (
configuration:
| WidgetConfiguration
| FieldsConfiguration
| FieldConfiguration
| null
| undefined,
): configuration is ChartConfiguration => {
return (
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'PieChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'GaugeChartConfiguration')
);
};
@@ -1,8 +1,7 @@
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
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 { isBarOrLineChartConfiguration } from '@/command-menu/pages/page-layout/utils/isBarOrLineChartConfiguration';
import { isPieChartConfiguration } from '@/command-menu/pages/page-layout/utils/isPieChartConfiguration';
import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
import { isRelationNestedFieldDateKind } from '@/page-layout/widgets/graph/utils/isRelationNestedFieldDateKind';
@@ -55,8 +54,12 @@ export const shouldHideChartSetting = (
);
if (isDefined(configuration) && isDefined(objectMetadataItem)) {
const isBarOrLineChart =
isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
isWidgetConfigurationOfType(configuration, 'LineChartConfiguration');
if (item.id === CHART_CONFIGURATION_SETTING_IDS.DATE_GRANULARITY_X) {
if (isBarOrLineChartConfiguration(configuration)) {
if (isBarOrLineChart) {
return shouldHideDateGranularityBasedOnFieldType(
configuration.primaryAxisGroupByFieldMetadataId,
configuration.primaryAxisGroupBySubFieldName,
@@ -67,7 +70,7 @@ export const shouldHideChartSetting = (
}
if (item.id === CHART_CONFIGURATION_SETTING_IDS.DATE_GRANULARITY_Y) {
if (isBarOrLineChartConfiguration(configuration)) {
if (isBarOrLineChart) {
return shouldHideDateGranularityBasedOnFieldType(
configuration.secondaryAxisGroupByFieldMetadataId,
configuration.secondaryAxisGroupBySubFieldName,
@@ -78,7 +81,7 @@ export const shouldHideChartSetting = (
}
if (item.id === CHART_CONFIGURATION_SETTING_IDS.DATE_GRANULARITY) {
if (isPieChartConfiguration(configuration)) {
if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
return shouldHideDateGranularityBasedOnFieldType(
configuration.groupByFieldMetadataId,
configuration.groupBySubFieldName,
@@ -89,7 +92,7 @@ export const shouldHideChartSetting = (
}
if (item.id === CHART_CONFIGURATION_SETTING_IDS.CUMULATIVE) {
if (isBarOrLineChartConfiguration(configuration)) {
if (isBarOrLineChart) {
return shouldHideDateGranularityBasedOnFieldType(
configuration.primaryAxisGroupByFieldMetadataId,
configuration.primaryAxisGroupBySubFieldName,
@@ -100,7 +103,7 @@ export const shouldHideChartSetting = (
}
if (item.id === CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND) {
if (isPieChartConfiguration(configuration)) {
if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
return false;
}
}