Split bar graph into two distinct horizontal and vertical bars (#15061)
### Summary Split BAR into VERTICAL_BAR and HORIZONTAL_BAR as separate chart types. ### The Problem Initially wanted a simple vertical/horizontal toggle for bar charts, but ran into a GraphQL union type constraint: union types can't have the same field name with different nullability. - Vertical bars need groupByFieldMetadataIdX as required (categories on X) - Horizontal bars need groupByFieldMetadataIdY as required (categories on Y) - GraphQL schema generation fails with this setup ### The Solution Use semantic primaryAxis and secondaryAxis naming that's orientation-agnostic: - primaryAxisGroupByFieldMetadataId = main grouping field (e.g., "Company Name") - secondaryAxisGroupByFieldMetadataId = optional secondary grouping (e.g., "Stage") These fields have consistent meaning regardless of orientation. The visual mapping happens at the UI layer: - Vertical bars: primary data renders on X-axis, secondary on Y-axis - Horizontal bars: primary data renders on Y-axis, secondary on X-axis Both chart types share the same DTO structure with consistent nullability. ### What Changed - Split GraphType.BAR → VERTICAL_BAR | HORIZONTAL_BAR - Renamed fields: primaryAxisGroupByFieldMetadataId, secondaryAxisGroupByFieldMetadataId (+ subfield variants) - useChartSettingsValues(): Maps semantic fields to setting values (no swapping) - getBarChartSettings(): Dynamically arranges settings panel based on orientation - transformGroupByDataToBarChartData(): Maps semantic fields to Nivo's layout prop video QA https://github.com/user-attachments/assets/479061b5-712e-4ca6-9858-95273d1f16c1
This commit is contained in:
@@ -287,16 +287,16 @@ export type BarChartConfiguration = {
|
||||
displayDataLabel?: Maybe<Scalars['Boolean']>;
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
graphType: GraphType;
|
||||
groupByFieldMetadataIdX: Scalars['UUID'];
|
||||
groupByFieldMetadataIdY?: Maybe<Scalars['UUID']>;
|
||||
groupBySubFieldNameX?: Maybe<Scalars['String']>;
|
||||
groupBySubFieldNameY?: Maybe<Scalars['String']>;
|
||||
groupMode?: Maybe<BarChartGroupMode>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
orderByX?: Maybe<GraphOrderBy>;
|
||||
orderByY?: Maybe<GraphOrderBy>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
primaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
primaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
rangeMax?: Maybe<Scalars['Float']>;
|
||||
rangeMin?: Maybe<Scalars['Float']>;
|
||||
secondaryAxisGroupByFieldMetadataId?: Maybe<Scalars['UUID']>;
|
||||
secondaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
secondaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
};
|
||||
|
||||
/** Display mode for bar charts with secondary grouping */
|
||||
@@ -1448,11 +1448,12 @@ export enum GraphOrderBy {
|
||||
|
||||
/** Type of graph widget */
|
||||
export enum GraphType {
|
||||
BAR = 'BAR',
|
||||
GAUGE = 'GAUGE',
|
||||
HORIZONTAL_BAR = 'HORIZONTAL_BAR',
|
||||
LINE = 'LINE',
|
||||
NUMBER = 'NUMBER',
|
||||
PIE = 'PIE'
|
||||
PIE = 'PIE',
|
||||
VERTICAL_BAR = 'VERTICAL_BAR'
|
||||
}
|
||||
|
||||
export type GridPosition = {
|
||||
@@ -1632,15 +1633,15 @@ export type LineChartConfiguration = {
|
||||
displayDataLabel?: Maybe<Scalars['Boolean']>;
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
graphType: GraphType;
|
||||
groupByFieldMetadataIdX: Scalars['UUID'];
|
||||
groupByFieldMetadataIdY?: Maybe<Scalars['UUID']>;
|
||||
groupBySubFieldNameX?: Maybe<Scalars['String']>;
|
||||
groupBySubFieldNameY?: Maybe<Scalars['String']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
orderByX?: Maybe<GraphOrderBy>;
|
||||
orderByY?: Maybe<GraphOrderBy>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
primaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
primaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
rangeMax?: Maybe<Scalars['Float']>;
|
||||
rangeMin?: Maybe<Scalars['Float']>;
|
||||
secondaryAxisGroupByFieldMetadataId?: Maybe<Scalars['UUID']>;
|
||||
secondaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
secondaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
};
|
||||
|
||||
export type LinkMetadata = {
|
||||
|
||||
@@ -287,16 +287,16 @@ export type BarChartConfiguration = {
|
||||
displayDataLabel?: Maybe<Scalars['Boolean']>;
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
graphType: GraphType;
|
||||
groupByFieldMetadataIdX: Scalars['UUID'];
|
||||
groupByFieldMetadataIdY?: Maybe<Scalars['UUID']>;
|
||||
groupBySubFieldNameX?: Maybe<Scalars['String']>;
|
||||
groupBySubFieldNameY?: Maybe<Scalars['String']>;
|
||||
groupMode?: Maybe<BarChartGroupMode>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
orderByX?: Maybe<GraphOrderBy>;
|
||||
orderByY?: Maybe<GraphOrderBy>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
primaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
primaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
rangeMax?: Maybe<Scalars['Float']>;
|
||||
rangeMin?: Maybe<Scalars['Float']>;
|
||||
secondaryAxisGroupByFieldMetadataId?: Maybe<Scalars['UUID']>;
|
||||
secondaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
secondaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
};
|
||||
|
||||
/** Display mode for bar charts with secondary grouping */
|
||||
@@ -1405,11 +1405,12 @@ export enum GraphOrderBy {
|
||||
|
||||
/** Type of graph widget */
|
||||
export enum GraphType {
|
||||
BAR = 'BAR',
|
||||
GAUGE = 'GAUGE',
|
||||
HORIZONTAL_BAR = 'HORIZONTAL_BAR',
|
||||
LINE = 'LINE',
|
||||
NUMBER = 'NUMBER',
|
||||
PIE = 'PIE'
|
||||
PIE = 'PIE',
|
||||
VERTICAL_BAR = 'VERTICAL_BAR'
|
||||
}
|
||||
|
||||
export type GridPosition = {
|
||||
@@ -1589,15 +1590,15 @@ export type LineChartConfiguration = {
|
||||
displayDataLabel?: Maybe<Scalars['Boolean']>;
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
graphType: GraphType;
|
||||
groupByFieldMetadataIdX: Scalars['UUID'];
|
||||
groupByFieldMetadataIdY?: Maybe<Scalars['UUID']>;
|
||||
groupBySubFieldNameX?: Maybe<Scalars['String']>;
|
||||
groupBySubFieldNameY?: Maybe<Scalars['String']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
orderByX?: Maybe<GraphOrderBy>;
|
||||
orderByY?: Maybe<GraphOrderBy>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
primaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
primaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
rangeMax?: Maybe<Scalars['Float']>;
|
||||
rangeMin?: Maybe<Scalars['Float']>;
|
||||
secondaryAxisGroupByFieldMetadataId?: Maybe<Scalars['UUID']>;
|
||||
secondaryAxisGroupBySubFieldName?: Maybe<Scalars['String']>;
|
||||
secondaryAxisOrderBy?: Maybe<GraphOrderBy>;
|
||||
};
|
||||
|
||||
export type LinkMetadata = {
|
||||
@@ -4487,7 +4488,7 @@ export type SearchQueryVariables = Exact<{
|
||||
|
||||
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
|
||||
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupByFieldMetadataIdX: any, groupBySubFieldNameX?: string | null, orderByX?: GraphOrderBy | null, groupByFieldMetadataIdY?: any | null, groupBySubFieldNameY?: string | null, orderByY?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupByFieldMetadataIdX: any, groupBySubFieldNameX?: string | null, orderByX?: GraphOrderBy | null, groupByFieldMetadataIdY?: any | null, groupBySubFieldNameY?: string | null, orderByY?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'NumberChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, description?: string | null, filter?: any | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupBySubFieldName?: string | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | null };
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'NumberChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, description?: string | null, filter?: any | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupBySubFieldName?: string | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | null };
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4495,7 +4496,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupByFieldMetadataIdX: any, groupBySubFieldNameX?: string | null, orderByX?: GraphOrderBy | null, groupByFieldMetadataIdY?: any | null, groupBySubFieldNameY?: string | null, orderByY?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupByFieldMetadataIdX: any, groupBySubFieldNameX?: string | null, orderByX?: GraphOrderBy | null, groupByFieldMetadataIdY?: any | null, groupBySubFieldNameY?: string | null, orderByY?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'NumberChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, description?: string | null, filter?: any | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupBySubFieldName?: string | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | null }> | null }> | null } };
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'IframeConfiguration', url: string } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null } | { __typename?: 'NumberChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, displayDataLabel?: boolean | null, description?: string | null, filter?: any | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: ExtendedAggregateOperations, groupBySubFieldName?: string | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null } | null }> | null }> | null } };
|
||||
|
||||
export type OnDbEventSubscriptionVariables = Exact<{
|
||||
input: OnDbEventInput;
|
||||
@@ -4796,12 +4797,12 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
groupBySubFieldNameX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
groupBySubFieldNameY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
@@ -4816,12 +4817,12 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
groupBySubFieldNameX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
groupBySubFieldNameY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@ import { t } from '@lingui/core/macro';
|
||||
import { MenuPicker } from 'twenty-ui/navigation';
|
||||
|
||||
const graphTypeOptions = [
|
||||
GraphType.BAR,
|
||||
GraphType.VERTICAL_BAR,
|
||||
GraphType.HORIZONTAL_BAR,
|
||||
GraphType.NUMBER,
|
||||
GraphType.PIE,
|
||||
GraphType.LINE,
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const CommandMenuPageLayoutWidgetTypeSelect = () => {
|
||||
if (!isDefined(pageLayoutEditingWidgetId)) {
|
||||
const fieldSelection = buildBarChartFieldSelection();
|
||||
const newWidget = createPageLayoutGraphWidget({
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
fieldSelection,
|
||||
});
|
||||
|
||||
|
||||
+6
-4
@@ -86,10 +86,12 @@ export const ChartDataSourceDropdownContent = () => {
|
||||
objectMetadataId,
|
||||
configToUpdate: {
|
||||
aggregateFieldMetadataId: null,
|
||||
groupByFieldMetadataIdX: null,
|
||||
groupByFieldMetadataIdY: null,
|
||||
groupBySubFieldNameX: null,
|
||||
groupBySubFieldNameY: null,
|
||||
primaryAxisGroupByFieldMetadataId: null,
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
secondaryAxisGroupByFieldMetadataId: null,
|
||||
secondaryAxisGroupBySubFieldName: null,
|
||||
primaryAxisOrderBy: null,
|
||||
secondaryAxisOrderBy: null,
|
||||
groupByFieldMetadataId: null,
|
||||
groupBySubFieldName: null,
|
||||
},
|
||||
|
||||
+7
-10
@@ -14,7 +14,7 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
|
||||
@@ -27,22 +27,23 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const currentFieldMetadataId =
|
||||
widgetInEditMode.configuration.groupByFieldMetadataIdY;
|
||||
const currentFieldMetadataId = configuration.aggregateFieldMetadataId;
|
||||
|
||||
const [selectedFieldMetadataId, setSelectedFieldMetadataId] = useState(
|
||||
currentFieldMetadataId,
|
||||
);
|
||||
|
||||
const sourceObjectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === widgetInEditMode.objectMetadataId,
|
||||
(item) => item.id === widgetInEditMode?.objectMetadataId,
|
||||
);
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
@@ -68,10 +69,6 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
if (!isDefined(sourceObjectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isSubMenuOpen) {
|
||||
return (
|
||||
<ChartAggregateOperationSelectionDropdownContent
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ export const ChartGroupByFieldSelectionDropdownContent = () => {
|
||||
<ChartGroupByFieldSelectionDropdownContentBase<
|
||||
BarChartConfiguration | LineChartConfiguration
|
||||
>
|
||||
fieldMetadataIdKey="groupByFieldMetadataIdY"
|
||||
subFieldNameKey="groupBySubFieldNameY"
|
||||
fieldMetadataIdKey="secondaryAxisGroupByFieldMetadataId"
|
||||
subFieldNameKey="secondaryAxisGroupBySubFieldName"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-18
@@ -13,21 +13,20 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
type GraphOrderBy,
|
||||
type LineChartConfiguration,
|
||||
} from '~/generated/graphql';
|
||||
import { type GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
|
||||
const configuration = widgetInEditMode?.configuration as
|
||||
| BarChartConfiguration
|
||||
| LineChartConfiguration;
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
const currentOrderBy = configuration.orderByY;
|
||||
if (
|
||||
configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
@@ -45,9 +44,7 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
|
||||
const handleSelectSortOption = (orderBy: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
orderByY: orderBy,
|
||||
},
|
||||
configToUpdate: { secondaryAxisOrderBy: orderBy },
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
@@ -78,13 +75,11 @@ export const ChartSortByGroupByFieldDropdownContent = () => {
|
||||
text={getGroupBySortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataId:
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? configuration.groupByFieldMetadataIdY
|
||||
: 'groupByFieldMetadataId' in configuration
|
||||
? configuration.groupByFieldMetadataId
|
||||
: undefined,
|
||||
configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
})}
|
||||
selected={currentOrderBy === sortOption.value}
|
||||
selected={
|
||||
configuration.secondaryAxisOrderBy === sortOption.value
|
||||
}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
onClick={() => {
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ export const ChartXAxisFieldSelectionDropdownContent = () => {
|
||||
<ChartGroupByFieldSelectionDropdownContentBase<
|
||||
BarChartConfiguration | LineChartConfiguration
|
||||
>
|
||||
fieldMetadataIdKey="groupByFieldMetadataIdX"
|
||||
subFieldNameKey="groupBySubFieldNameX"
|
||||
fieldMetadataIdKey="primaryAxisGroupByFieldMetadataId"
|
||||
subFieldNameKey="primaryAxisGroupBySubFieldName"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+20
-20
@@ -11,24 +11,22 @@ import { SelectableListItem } from '@/ui/layout/selectable-list/components/Selec
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { MenuItemSelect } from 'twenty-ui/navigation';
|
||||
import { type GraphOrderBy } from '~/generated/graphql';
|
||||
|
||||
export const ChartXAxisSortBySelectionDropdownContent = () => {
|
||||
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
||||
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
|
||||
if (
|
||||
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration'
|
||||
configuration?.__typename !== 'BarChartConfiguration' &&
|
||||
configuration?.__typename !== 'LineChartConfiguration'
|
||||
) {
|
||||
throw new Error('Invalid configuration type');
|
||||
}
|
||||
|
||||
const configuration = widgetInEditMode?.configuration;
|
||||
const currentOrderByX = configuration.orderByX;
|
||||
|
||||
const dropdownId = useAvailableComponentInstanceIdOrThrow(
|
||||
DropdownComponentInstanceContext,
|
||||
);
|
||||
@@ -40,22 +38,19 @@ export const ChartXAxisSortBySelectionDropdownContent = () => {
|
||||
|
||||
const { updateCurrentWidgetConfig } =
|
||||
useUpdateCurrentWidgetConfig(pageLayoutId);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleSelectSortOption = (orderByX: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: {
|
||||
orderByX,
|
||||
},
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const { getXSortOptionLabel } = useGraphXSortOptionLabels({
|
||||
objectMetadataId: widgetInEditMode?.objectMetadataId,
|
||||
});
|
||||
|
||||
const handleSelect = (orderBy: GraphOrderBy) => {
|
||||
updateCurrentWidgetConfig({
|
||||
configToUpdate: { primaryAxisOrderBy: orderBy },
|
||||
});
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
@@ -68,23 +63,28 @@ export const ChartXAxisSortBySelectionDropdownContent = () => {
|
||||
key={sortOption.value}
|
||||
itemId={sortOption.value}
|
||||
onEnter={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
handleSelect(sortOption.value);
|
||||
}}
|
||||
>
|
||||
<MenuItemSelect
|
||||
text={getXSortOptionLabel({
|
||||
graphOrderBy: sortOption.value,
|
||||
groupByFieldMetadataIdX: configuration.groupByFieldMetadataIdX,
|
||||
groupByFieldMetadataIdX:
|
||||
configuration.primaryAxisGroupByFieldMetadataId,
|
||||
groupBySubFieldNameX:
|
||||
configuration.primaryAxisGroupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined,
|
||||
aggregateFieldMetadataId:
|
||||
configuration.aggregateFieldMetadataId ?? undefined,
|
||||
aggregateOperation:
|
||||
configuration.aggregateOperation ?? undefined,
|
||||
})}
|
||||
selected={currentOrderByX === sortOption.value}
|
||||
selected={configuration.primaryAxisOrderBy === sortOption.value}
|
||||
focused={selectedItemId === sortOption.value}
|
||||
LeftIcon={sortOption.icon}
|
||||
onClick={() => {
|
||||
handleSelectSortOption(sortOption.value);
|
||||
handleSelect(sortOption.value);
|
||||
}}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
|
||||
+10
-4
@@ -1,14 +1,15 @@
|
||||
import { BAR_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/BarChartSettings';
|
||||
import { GAUGE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/GaugeChartSettings';
|
||||
import { LINE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/LineChartSettings';
|
||||
import { NUMBER_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/NumberChartSettings';
|
||||
import { PIE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/PieChartSettings';
|
||||
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
|
||||
import { getBarChartSettings } from '@/command-menu/pages/page-layout/utils/getBarChartSettings';
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
Icon123,
|
||||
IconChartBar,
|
||||
IconChartBarHorizontal,
|
||||
IconChartLine,
|
||||
IconChartPie,
|
||||
type IconComponent,
|
||||
@@ -24,10 +25,15 @@ export const GRAPH_TYPE_INFORMATION: Record<
|
||||
settings: ChartSettingsGroup[];
|
||||
}
|
||||
> = {
|
||||
[GraphType.BAR]: {
|
||||
label: msg`Bar`,
|
||||
[GraphType.VERTICAL_BAR]: {
|
||||
label: msg`Vertical Bar`,
|
||||
icon: IconChartBar,
|
||||
settings: BAR_CHART_SETTINGS,
|
||||
settings: getBarChartSettings(GraphType.VERTICAL_BAR),
|
||||
},
|
||||
[GraphType.HORIZONTAL_BAR]: {
|
||||
label: msg`Horizontal Bar`,
|
||||
icon: IconChartBarHorizontal,
|
||||
settings: getBarChartSettings(GraphType.HORIZONTAL_BAR),
|
||||
},
|
||||
[GraphType.PIE]: {
|
||||
label: msg`Pie`,
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const GRAPH_TYPE_TO_CONFIG_TYPENAME = {
|
||||
[GraphType.BAR]: 'BarChartConfiguration',
|
||||
[GraphType.VERTICAL_BAR]: 'BarChartConfiguration',
|
||||
[GraphType.HORIZONTAL_BAR]: 'BarChartConfiguration',
|
||||
[GraphType.LINE]: 'LineChartConfiguration',
|
||||
[GraphType.PIE]: 'PieChartConfiguration',
|
||||
[GraphType.NUMBER]: 'NumberChartConfiguration',
|
||||
|
||||
+592
@@ -0,0 +1,592 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
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 { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
AxisNameDisplay,
|
||||
type BarChartConfiguration,
|
||||
BarChartGroupMode,
|
||||
ExtendedAggregateOperations,
|
||||
FieldMetadataType,
|
||||
GraphOrderBy,
|
||||
GraphType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useChartSettingsValues } from '../useChartSettingsValues';
|
||||
|
||||
const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
id: 'obj-1',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
labelSingular: 'Company',
|
||||
labelPlural: 'Companies',
|
||||
fields: [
|
||||
{
|
||||
id: 'field-company-name',
|
||||
name: 'name',
|
||||
label: 'Company Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
{
|
||||
id: 'field-amount',
|
||||
name: 'amount',
|
||||
label: 'Amount',
|
||||
type: FieldMetadataType.NUMBER,
|
||||
},
|
||||
{
|
||||
id: 'field-stage',
|
||||
name: 'stage',
|
||||
label: 'Stage',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
{
|
||||
id: 'field-full-name',
|
||||
name: 'fullName',
|
||||
label: 'Full Name',
|
||||
type: FieldMetadataType.FULL_NAME,
|
||||
subFields: [
|
||||
{
|
||||
id: 'field-full-name-first',
|
||||
name: 'firstName',
|
||||
label: 'First Name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
const buildBarChartConfiguration = (
|
||||
overrides: Partial<BarChartConfiguration>,
|
||||
): BarChartConfiguration =>
|
||||
({
|
||||
__typename: 'BarChartConfiguration',
|
||||
aggregateFieldMetadataId: 'field-amount',
|
||||
aggregateOperation: ExtendedAggregateOperations.SUM,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
secondaryAxisGroupByFieldMetadataId: null,
|
||||
secondaryAxisGroupBySubFieldName: null,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
secondaryAxisOrderBy: null,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
groupMode: 'GROUPED',
|
||||
...overrides,
|
||||
}) as BarChartConfiguration;
|
||||
|
||||
const renderUseChartSettingsValues = (configuration: ChartConfiguration) => {
|
||||
return renderHook(
|
||||
() =>
|
||||
useChartSettingsValues({
|
||||
objectMetadataId: mockObjectMetadataItem.id,
|
||||
configuration,
|
||||
}),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(objectMetadataItemsState, [mockObjectMetadataItem]);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecoilRoot>
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
describe('useChartSettingsValues', () => {
|
||||
describe('Vertical bar chart (1D - single grouping)', () => {
|
||||
const verticalBarConfig = buildBarChartConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: null,
|
||||
});
|
||||
|
||||
it('should return primary field label for DATA_ON_DISPLAY_X', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBe('Company Name');
|
||||
});
|
||||
|
||||
it('should return aggregate field with operation for DATA_ON_DISPLAY_Y', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y,
|
||||
);
|
||||
|
||||
expect(value).toBe('Amount (Sum)');
|
||||
});
|
||||
|
||||
it('should return object label plural for SOURCE', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
);
|
||||
|
||||
expect(value).toBe('Companies');
|
||||
});
|
||||
|
||||
it('should return sort label for SORT_BY_X', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X,
|
||||
);
|
||||
|
||||
expect(value).toBeDefined();
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
|
||||
it('should return displayDataLabel value for DATA_LABELS', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS,
|
||||
);
|
||||
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
|
||||
it('should return axis name display option for AXIS_NAME', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME,
|
||||
);
|
||||
|
||||
expect(value).toBeDefined();
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Horizontal bar chart (1D - single grouping)', () => {
|
||||
const horizontalBarConfig = buildBarChartConfiguration({
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: null,
|
||||
});
|
||||
|
||||
it('should return SAME primary field label for DATA_ON_DISPLAY_X (no swapping)', () => {
|
||||
const { result } = renderUseChartSettingsValues(horizontalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
// Critical test: horizontal should return the SAME value as vertical
|
||||
expect(value).toBe('Company Name');
|
||||
});
|
||||
|
||||
it('should return SAME aggregate field for DATA_ON_DISPLAY_Y (no swapping)', () => {
|
||||
const { result } = renderUseChartSettingsValues(horizontalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y,
|
||||
);
|
||||
|
||||
// Critical test: horizontal should return the SAME value as vertical
|
||||
expect(value).toBe('Amount (Sum)');
|
||||
});
|
||||
|
||||
it('should return SAME sort label for SORT_BY_X (no swapping)', () => {
|
||||
const { result } = renderUseChartSettingsValues(horizontalBarConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X,
|
||||
);
|
||||
|
||||
expect(value).toBeDefined();
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vertical bar chart (2D - with secondary grouping)', () => {
|
||||
const verticalBar2DConfig = buildBarChartConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-stage',
|
||||
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
});
|
||||
|
||||
it('should return primary field label for DATA_ON_DISPLAY_X', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBar2DConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBe('Company Name');
|
||||
});
|
||||
|
||||
it('should return secondary field label for GROUP_BY', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBar2DConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
|
||||
);
|
||||
|
||||
expect(value).toBe('Stage');
|
||||
});
|
||||
|
||||
it('should return secondary sort label for SORT_BY_GROUP_BY_FIELD', () => {
|
||||
const { result } = renderUseChartSettingsValues(verticalBar2DConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD,
|
||||
);
|
||||
|
||||
expect(value).toBeDefined();
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Horizontal bar chart (2D - with secondary grouping)', () => {
|
||||
const horizontalBar2DConfig = buildBarChartConfiguration({
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-stage',
|
||||
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
});
|
||||
|
||||
it('should return SAME primary field for DATA_ON_DISPLAY_X (no swapping)', () => {
|
||||
const { result } = renderUseChartSettingsValues(horizontalBar2DConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBe('Company Name');
|
||||
});
|
||||
|
||||
it('should return SAME secondary field for GROUP_BY (no swapping)', () => {
|
||||
const { result } = renderUseChartSettingsValues(horizontalBar2DConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
|
||||
);
|
||||
|
||||
expect(value).toBe('Stage');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Composite field with subfield', () => {
|
||||
const configWithSubField = buildBarChartConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-full-name',
|
||||
primaryAxisGroupBySubFieldName: 'firstName',
|
||||
});
|
||||
|
||||
it('should return field label with subfield label for DATA_ON_DISPLAY_X', () => {
|
||||
const { result } = renderUseChartSettingsValues(configWithSubField);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toContain('Full Name');
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stacked bars setting', () => {
|
||||
it('should return false when groupMode is GROUPED', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
groupMode: BarChartGroupMode.GROUPED,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.STACKED_BARS,
|
||||
);
|
||||
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when groupMode is STACKED', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
groupMode: BarChartGroupMode.STACKED,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.STACKED_BARS,
|
||||
);
|
||||
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Colors setting', () => {
|
||||
it('should return undefined when no color is set', () => {
|
||||
const config = buildBarChartConfiguration({});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.COLORS,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return capitalized color when color is set', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
color: 'blue',
|
||||
} as Partial<BarChartConfiguration>);
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.COLORS,
|
||||
);
|
||||
|
||||
expect(value).toBe('Blue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('No configuration', () => {
|
||||
it('should return undefined function when configuration is undefined', () => {
|
||||
const { result } = renderUseChartSettingsValues(undefined as any);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases - Missing fields', () => {
|
||||
it('should handle missing primaryAxisGroupByFieldMetadataId gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
primaryAxisGroupByFieldMetadataId: 'non-existent-field-id',
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle missing aggregateFieldMetadataId gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
aggregateFieldMetadataId: 'non-existent-aggregate-field',
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y,
|
||||
);
|
||||
|
||||
expect(value).toBe('');
|
||||
});
|
||||
|
||||
it('should handle missing secondaryAxisGroupByFieldMetadataId gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
secondaryAxisGroupByFieldMetadataId: 'non-existent-secondary-field',
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle missing objectMetadataItem gracefully', () => {
|
||||
const config = buildBarChartConfiguration({});
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useChartSettingsValues({
|
||||
objectMetadataId: 'non-existent-object-id',
|
||||
configuration: config,
|
||||
}),
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(objectMetadataItemsState, [mockObjectMetadataItem]);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</RecoilRoot>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases - Null/undefined orderBy', () => {
|
||||
it('should handle null primaryAxisOrderBy gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
primaryAxisOrderBy: null,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle null secondaryAxisOrderBy gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-stage',
|
||||
secondaryAxisOrderBy: null,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases - Missing axisNameDisplay', () => {
|
||||
it('should handle undefined axisNameDisplay gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
axisNameDisplay: undefined as any,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME,
|
||||
);
|
||||
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases - Composite field with missing subfield', () => {
|
||||
it('should handle composite field without subfield gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
primaryAxisGroupByFieldMetadataId: 'field-full-name',
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBe('Full Name');
|
||||
});
|
||||
|
||||
it('should handle invalid subfield name gracefully', () => {
|
||||
const config = buildBarChartConfiguration({
|
||||
primaryAxisGroupByFieldMetadataId: 'field-full-name',
|
||||
primaryAxisGroupBySubFieldName: 'invalidSubField' as any,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(config);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBeDefined();
|
||||
expect(typeof value).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
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,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: null,
|
||||
});
|
||||
|
||||
const { result } = renderUseChartSettingsValues(horizontalConfig);
|
||||
|
||||
const value = result.current.getChartSettingsValues(
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
);
|
||||
|
||||
expect(value).toBe('Company Name');
|
||||
expect(value).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return identical values for all settings regardless of orientation', () => {
|
||||
const baseConfig = {
|
||||
primaryAxisGroupByFieldMetadataId: 'field-company-name',
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-stage',
|
||||
aggregateFieldMetadataId: 'field-amount',
|
||||
aggregateOperation: ExtendedAggregateOperations.SUM,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: true,
|
||||
};
|
||||
|
||||
const verticalConfig = buildBarChartConfiguration({
|
||||
...baseConfig,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
});
|
||||
|
||||
const horizontalConfig = buildBarChartConfiguration({
|
||||
...baseConfig,
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
});
|
||||
|
||||
const { result: verticalResult } =
|
||||
renderUseChartSettingsValues(verticalConfig);
|
||||
const { result: horizontalResult } =
|
||||
renderUseChartSettingsValues(horizontalConfig);
|
||||
|
||||
// Test all setting IDs return IDENTICAL values
|
||||
const settingIds = [
|
||||
CHART_CONFIGURATION_SETTING_IDS.SOURCE,
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X,
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y,
|
||||
CHART_CONFIGURATION_SETTING_IDS.GROUP_BY,
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X,
|
||||
CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD,
|
||||
CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME,
|
||||
CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS,
|
||||
];
|
||||
|
||||
settingIds.forEach((settingId) => {
|
||||
const verticalValue =
|
||||
verticalResult.current.getChartSettingsValues(settingId);
|
||||
const horizontalValue =
|
||||
horizontalResult.current.getChartSettingsValues(settingId);
|
||||
|
||||
expect(verticalValue).toEqual(horizontalValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+55
-48
@@ -9,6 +9,7 @@ import { getAggregateOperationLabel } from '@/object-record/record-board/record-
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { type GraphOrderBy } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useChartSettingsValues = ({
|
||||
objectMetadataId,
|
||||
@@ -37,20 +38,43 @@ export const useChartSettingsValues = ({
|
||||
};
|
||||
}
|
||||
|
||||
const groupByFieldX =
|
||||
'groupByFieldMetadataIdX' in configuration
|
||||
? objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === configuration.groupByFieldMetadataIdX,
|
||||
)
|
||||
: undefined;
|
||||
const isBarOrLineChart =
|
||||
configuration.__typename === 'BarChartConfiguration' ||
|
||||
configuration.__typename === 'LineChartConfiguration';
|
||||
|
||||
let groupByFieldXId: string | undefined;
|
||||
let groupByFieldYId: string | undefined;
|
||||
let groupBySubFieldNameX: CompositeFieldSubFieldName | undefined;
|
||||
let groupBySubFieldNameY: CompositeFieldSubFieldName | undefined;
|
||||
let xAxisOrderBy: GraphOrderBy | undefined | null;
|
||||
let groupByOrderBy: GraphOrderBy | undefined | null;
|
||||
|
||||
if (isBarOrLineChart) {
|
||||
groupByFieldXId = configuration.primaryAxisGroupByFieldMetadataId;
|
||||
groupByFieldYId = configuration.secondaryAxisGroupByFieldMetadataId;
|
||||
groupBySubFieldNameX = configuration.primaryAxisGroupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined;
|
||||
groupBySubFieldNameY = configuration.secondaryAxisGroupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined;
|
||||
xAxisOrderBy = configuration.primaryAxisOrderBy;
|
||||
groupByOrderBy = configuration.secondaryAxisOrderBy;
|
||||
}
|
||||
|
||||
const groupByFieldX = isDefined(groupByFieldXId)
|
||||
? objectMetadataItem?.fields.find((field) => field.id === groupByFieldXId)
|
||||
: undefined;
|
||||
|
||||
const groupByFieldY = isDefined(groupByFieldYId)
|
||||
? objectMetadataItem?.fields.find((field) => field.id === groupByFieldYId)
|
||||
: undefined;
|
||||
|
||||
const groupBySubFieldNameXLabel =
|
||||
'groupBySubFieldNameX' in configuration && isDefined(groupByFieldX)
|
||||
isDefined(groupBySubFieldNameX) && isDefined(groupByFieldX)
|
||||
? getFieldLabelWithSubField({
|
||||
field: groupByFieldX,
|
||||
subFieldName:
|
||||
configuration.groupBySubFieldNameX as CompositeFieldSubFieldName,
|
||||
subFieldName: groupBySubFieldNameX,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
@@ -61,53 +85,36 @@ export const useChartSettingsValues = ({
|
||||
|
||||
const yAxisAggregateOperation = configuration.aggregateOperation;
|
||||
|
||||
const groupByFieldY =
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? objectMetadataItem?.fields.find(
|
||||
(fieldMetadataItem) =>
|
||||
fieldMetadataItem.id === configuration.groupByFieldMetadataIdY,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const xAxisOrderBy =
|
||||
'orderByX' in configuration ? configuration.orderByX : undefined;
|
||||
|
||||
const xAxisOrderByLabel =
|
||||
isDefined(xAxisOrderBy) && 'groupByFieldMetadataIdX' in configuration
|
||||
isDefined(xAxisOrderBy) && isDefined(groupByFieldXId)
|
||||
? getXSortOptionLabel({
|
||||
graphOrderBy: xAxisOrderBy,
|
||||
groupByFieldMetadataIdX: configuration.groupByFieldMetadataIdX,
|
||||
groupBySubFieldNameX:
|
||||
configuration.groupBySubFieldNameX as CompositeFieldSubFieldName,
|
||||
groupByFieldMetadataIdX: groupByFieldXId,
|
||||
groupBySubFieldNameX: groupBySubFieldNameX,
|
||||
aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
|
||||
aggregateOperation: configuration.aggregateOperation ?? undefined,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const groupByOrderBy =
|
||||
'orderByY' in configuration
|
||||
? configuration.orderByY
|
||||
: 'orderBy' in configuration
|
||||
? configuration.orderBy
|
||||
: undefined;
|
||||
if (configuration.__typename === 'PieChartConfiguration') {
|
||||
groupByOrderBy = configuration.orderBy;
|
||||
groupByFieldYId = configuration.groupByFieldMetadataId;
|
||||
groupBySubFieldNameY = configuration.groupBySubFieldName as
|
||||
| CompositeFieldSubFieldName
|
||||
| undefined;
|
||||
}
|
||||
|
||||
const finalGroupByFieldYId = groupByFieldYId;
|
||||
const finalGroupBySubFieldNameY = groupBySubFieldNameY;
|
||||
|
||||
const groupByOrderByLabel =
|
||||
isDefined(groupByOrderBy) &&
|
||||
getGroupBySortOptionLabel({
|
||||
graphOrderBy: groupByOrderBy,
|
||||
groupByFieldMetadataId:
|
||||
'groupByFieldMetadataIdY' in configuration
|
||||
? configuration.groupByFieldMetadataIdY
|
||||
: 'groupByFieldMetadataId' in configuration
|
||||
? configuration.groupByFieldMetadataId
|
||||
: undefined,
|
||||
groupBySubFieldName:
|
||||
'groupBySubFieldNameY' in configuration
|
||||
? (configuration.groupBySubFieldNameY as CompositeFieldSubFieldName)
|
||||
: 'groupBySubFieldName' in configuration
|
||||
? (configuration.groupBySubFieldName as CompositeFieldSubFieldName)
|
||||
: undefined,
|
||||
});
|
||||
isDefined(groupByOrderBy) && isDefined(finalGroupByFieldYId)
|
||||
? getGroupBySortOptionLabel({
|
||||
graphOrderBy: groupByOrderBy,
|
||||
groupByFieldMetadataId: finalGroupByFieldYId,
|
||||
groupBySubFieldName: finalGroupBySubFieldNameY,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const getChartSettingsValues = (
|
||||
itemId: CHART_CONFIGURATION_SETTING_IDS,
|
||||
@@ -119,7 +126,7 @@ export const useChartSettingsValues = ({
|
||||
return groupBySubFieldNameXLabel ?? groupByFieldX?.label;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.COLORS:
|
||||
return 'color' in configuration && isDefined(configuration.color)
|
||||
? capitalize(configuration.color as string)
|
||||
? capitalize(configuration.color)
|
||||
: undefined;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y: {
|
||||
const hasAggregateLabel = isDefined(aggregateField?.label);
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
|
||||
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
|
||||
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
|
||||
import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting';
|
||||
import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting';
|
||||
import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting';
|
||||
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 '../getBarChartSettings';
|
||||
|
||||
describe('getBarChartSettings', () => {
|
||||
describe('Vertical bar chart', () => {
|
||||
it('should place primary axis items under "X axis" heading', () => {
|
||||
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
|
||||
|
||||
const xAxisGroup = result.find((group) => group.heading === 'X axis');
|
||||
|
||||
expect(xAxisGroup).toBeDefined();
|
||||
expect(xAxisGroup?.items).toHaveLength(2);
|
||||
expect(xAxisGroup?.items[0].id).toBe(DATA_DISPLAY_X_SETTING.id);
|
||||
expect(xAxisGroup?.items[0].label).toBe(DATA_DISPLAY_X_SETTING.label);
|
||||
expect(xAxisGroup?.items[0].Icon).toBe(IconAxisX);
|
||||
expect(xAxisGroup?.items[1]).toEqual(SORT_BY_X_SETTING);
|
||||
});
|
||||
|
||||
it('should place secondary axis items under "Y axis" heading', () => {
|
||||
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
|
||||
|
||||
const yAxisGroup = result.find((group) => group.heading === 'Y axis');
|
||||
|
||||
expect(yAxisGroup).toBeDefined();
|
||||
expect(yAxisGroup?.items).toHaveLength(3);
|
||||
expect(yAxisGroup?.items[0].id).toBe(DATA_DISPLAY_Y_SETTING.id);
|
||||
expect(yAxisGroup?.items[0].label).toBe(DATA_DISPLAY_Y_SETTING.label);
|
||||
expect(yAxisGroup?.items[0].Icon).toBe(IconAxisY);
|
||||
expect(yAxisGroup?.items[1]).toEqual(GROUP_BY_SETTING);
|
||||
expect(yAxisGroup?.items[2]).toEqual(SORT_BY_GROUP_BY_FIELD_SETTING);
|
||||
});
|
||||
|
||||
it('should have all expected groups in correct order', () => {
|
||||
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
|
||||
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result[0].heading).toBe('Data');
|
||||
expect(result[1].heading).toBe('X axis');
|
||||
expect(result[2].heading).toBe('Y axis');
|
||||
expect(result[3].heading).toBe('Style');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Horizontal bar chart', () => {
|
||||
it('should place SECONDARY axis items under "X axis" heading', () => {
|
||||
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
|
||||
|
||||
const xAxisGroup = result.find((group) => group.heading === 'X axis');
|
||||
|
||||
expect(xAxisGroup).toBeDefined();
|
||||
expect(xAxisGroup?.items).toHaveLength(3);
|
||||
expect(xAxisGroup?.items[0].id).toBe(DATA_DISPLAY_Y_SETTING.id);
|
||||
expect(xAxisGroup?.items[0].label).toBe(DATA_DISPLAY_Y_SETTING.label);
|
||||
expect(xAxisGroup?.items[0].Icon).toBe(IconAxisX);
|
||||
expect(xAxisGroup?.items[1]).toEqual(GROUP_BY_SETTING);
|
||||
expect(xAxisGroup?.items[2]).toEqual(SORT_BY_GROUP_BY_FIELD_SETTING);
|
||||
});
|
||||
|
||||
it('should place PRIMARY axis items under "Y axis" heading', () => {
|
||||
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
|
||||
|
||||
const yAxisGroup = result.find((group) => group.heading === 'Y axis');
|
||||
|
||||
expect(yAxisGroup).toBeDefined();
|
||||
expect(yAxisGroup?.items).toHaveLength(2);
|
||||
expect(yAxisGroup?.items[0].id).toBe(DATA_DISPLAY_X_SETTING.id);
|
||||
expect(yAxisGroup?.items[0].label).toBe(DATA_DISPLAY_X_SETTING.label);
|
||||
expect(yAxisGroup?.items[0].Icon).toBe(IconAxisY);
|
||||
expect(yAxisGroup?.items[1]).toEqual(SORT_BY_X_SETTING);
|
||||
});
|
||||
|
||||
it('should have all expected groups in correct order', () => {
|
||||
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
|
||||
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result[0].heading).toBe('Data');
|
||||
expect(result[1].heading).toBe('X axis');
|
||||
expect(result[2].heading).toBe('Y axis');
|
||||
expect(result[3].heading).toBe('Style');
|
||||
});
|
||||
});
|
||||
|
||||
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 verticalDataGroup = verticalResult.find(
|
||||
(group) => group.heading === 'Data',
|
||||
);
|
||||
const horizontalDataGroup = horizontalResult.find(
|
||||
(group) => group.heading === 'Data',
|
||||
);
|
||||
|
||||
expect(verticalDataGroup?.items).toEqual(horizontalDataGroup?.items);
|
||||
expect(verticalDataGroup?.items).toEqual([
|
||||
CHART_DATA_SOURCE_SETTING,
|
||||
FILTER_SETTING,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have consistent Style group for both orientations', () => {
|
||||
const verticalResult = getBarChartSettings(GraphType.VERTICAL_BAR);
|
||||
const horizontalResult = getBarChartSettings(GraphType.HORIZONTAL_BAR);
|
||||
|
||||
const verticalStyleGroup = verticalResult.find(
|
||||
(group) => group.heading === 'Style',
|
||||
);
|
||||
const horizontalStyleGroup = horizontalResult.find(
|
||||
(group) => group.heading === 'Style',
|
||||
);
|
||||
|
||||
expect(verticalStyleGroup?.items).toEqual(horizontalStyleGroup?.items);
|
||||
expect(verticalStyleGroup?.items).toEqual([
|
||||
COLORS_SETTING,
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_BARS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
+48
-27
@@ -10,31 +10,52 @@ import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout
|
||||
import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting';
|
||||
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';
|
||||
|
||||
export const BAR_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'X axis',
|
||||
items: [DATA_DISPLAY_X_SETTING, SORT_BY_X_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'Y axis',
|
||||
items: [
|
||||
DATA_DISPLAY_Y_SETTING,
|
||||
GROUP_BY_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [
|
||||
COLORS_SETTING,
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_BARS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
],
|
||||
},
|
||||
];
|
||||
export const getBarChartSettings = (
|
||||
graphType: GraphType.VERTICAL_BAR | GraphType.HORIZONTAL_BAR,
|
||||
): ChartSettingsGroup[] => {
|
||||
const isHorizontal = graphType === GraphType.HORIZONTAL_BAR;
|
||||
|
||||
const dataDisplayXIcon = isHorizontal ? IconAxisY : IconAxisX;
|
||||
const dataDisplayYIcon = isHorizontal ? IconAxisX : IconAxisY;
|
||||
|
||||
const primaryAxisItems = [
|
||||
{ ...DATA_DISPLAY_X_SETTING, Icon: dataDisplayXIcon },
|
||||
SORT_BY_X_SETTING,
|
||||
];
|
||||
|
||||
const secondaryAxisItems = [
|
||||
{ ...DATA_DISPLAY_Y_SETTING, Icon: dataDisplayYIcon },
|
||||
GROUP_BY_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
];
|
||||
|
||||
const xAxisItems = isHorizontal ? secondaryAxisItems : primaryAxisItems;
|
||||
const yAxisItems = isHorizontal ? primaryAxisItems : secondaryAxisItems;
|
||||
|
||||
return [
|
||||
{
|
||||
heading: 'Data',
|
||||
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
|
||||
},
|
||||
{
|
||||
heading: 'X axis',
|
||||
items: xAxisItems,
|
||||
},
|
||||
{
|
||||
heading: 'Y axis',
|
||||
items: yAxisItems,
|
||||
},
|
||||
{
|
||||
heading: 'Style',
|
||||
items: [
|
||||
COLORS_SETTING,
|
||||
AXIS_NAME_SETTING,
|
||||
STACKED_BARS_SETTING,
|
||||
DATA_LABELS_SETTING,
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
+7
-3
@@ -12,9 +12,13 @@ export const getFieldLabelWithSubField = ({
|
||||
field: FieldMetadataItem | undefined;
|
||||
subFieldName?: CompositeFieldSubFieldName;
|
||||
}): string => {
|
||||
const subFieldNameLabel = isDefined(subFieldName)
|
||||
? getCompositeSubFieldLabel(field?.type as CompositeFieldType, subFieldName)
|
||||
: undefined;
|
||||
const subFieldNameLabel =
|
||||
isDefined(subFieldName) && isDefined(field)
|
||||
? getCompositeSubFieldLabel(
|
||||
field.type as CompositeFieldType,
|
||||
subFieldName,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const fieldLabel =
|
||||
isDefined(subFieldNameLabel) && isDefined(field?.label)
|
||||
|
||||
+3
-3
@@ -162,11 +162,11 @@ const mixedGraphsPageLayoutMocks = {
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: nameField.id,
|
||||
groupByFieldMetadataIdX: createdAtField.id,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: createdAtField.id,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
} satisfies BarChartConfiguration,
|
||||
|
||||
+12
-12
@@ -20,12 +20,12 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
groupBySubFieldNameX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
groupBySubFieldNameY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
@@ -40,12 +40,12 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
groupBySubFieldNameX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
groupBySubFieldNameY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
|
||||
+4
-4
@@ -85,7 +85,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
|
||||
act(() => {
|
||||
result.current.createWidget.createPageLayoutGraphWidget({
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -163,7 +163,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
GraphType.NUMBER,
|
||||
GraphType.GAUGE,
|
||||
GraphType.PIE,
|
||||
GraphType.BAR,
|
||||
GraphType.VERTICAL_BAR,
|
||||
];
|
||||
|
||||
const mockFieldSelections: Partial<
|
||||
@@ -173,7 +173,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
objectMetadataId: 'test-object-id',
|
||||
aggregateFieldMetadataId: 'test-aggregate-field-id',
|
||||
},
|
||||
[GraphType.BAR]: {
|
||||
[GraphType.VERTICAL_BAR]: {
|
||||
objectMetadataId: 'test-object-id',
|
||||
groupByFieldMetadataIdX: 'test-groupby-field-id',
|
||||
aggregateFieldMetadataId: 'test-aggregate-field-id',
|
||||
@@ -234,7 +234,7 @@ describe('useCreatePageLayoutGraphWidget', () => {
|
||||
|
||||
expect(() => {
|
||||
result.current.createWidget.createPageLayoutGraphWidget({
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
});
|
||||
}).toThrow('A tab must be selected to create a new graph widget');
|
||||
});
|
||||
|
||||
+3
-3
@@ -103,11 +103,11 @@ describe('usePageLayoutDraftState', () => {
|
||||
type: WidgetType.GRAPH,
|
||||
gridPosition: { row: 2, column: 2, rowSpan: 2, columnSpan: 2 },
|
||||
configuration: {
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: 'id',
|
||||
groupByFieldMetadataIdX: 'createdAt',
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: 'createdAt',
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
objectMetadataId: null,
|
||||
|
||||
+13
-13
@@ -50,11 +50,11 @@ describe('extractFieldMetadataIdsFromWidget', () => {
|
||||
const widget = createMockWidget({
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: 'field-1',
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: 'field-2',
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -72,12 +72,12 @@ describe('extractFieldMetadataIdsFromWidget', () => {
|
||||
const widget = createMockWidget({
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: 'field-1',
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: 'field-2',
|
||||
groupByFieldMetadataIdY: 'field-3',
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-3',
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -99,8 +99,8 @@ describe('extractFieldMetadataIdsFromWidget', () => {
|
||||
graphType: GraphType.LINE,
|
||||
aggregateFieldMetadataId: 'field-1',
|
||||
aggregateOperation: ExtendedAggregateOperations.SUM,
|
||||
groupByFieldMetadataIdX: 'field-2',
|
||||
orderByX: GraphOrderBy.FIELD_DESC,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -175,12 +175,12 @@ describe('extractFieldMetadataIdsFromWidget', () => {
|
||||
const widget = createMockWidget({
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration' as const,
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: 'field-1',
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: 'field-2',
|
||||
groupByFieldMetadataIdY: undefined,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
secondaryAxisGroupByFieldMetadataId: undefined,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ const createDefaultGraphConfiguration = (
|
||||
case GraphType.PIE:
|
||||
return null;
|
||||
|
||||
case GraphType.BAR:
|
||||
case GraphType.VERTICAL_BAR:
|
||||
if (
|
||||
!isDefined(fieldSelection?.aggregateFieldMetadataId) ||
|
||||
!isDefined(fieldSelection?.groupByFieldMetadataIdX)
|
||||
@@ -44,13 +44,34 @@ const createDefaultGraphConfiguration = (
|
||||
}
|
||||
return {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
displayDataLabel: false,
|
||||
color: 'blue' satisfies ThemeColor,
|
||||
groupByFieldMetadataIdX: fieldSelection.groupByFieldMetadataIdX,
|
||||
primaryAxisGroupByFieldMetadataId:
|
||||
fieldSelection.groupByFieldMetadataIdX,
|
||||
aggregateFieldMetadataId: fieldSelection.aggregateFieldMetadataId,
|
||||
aggregateOperation: ExtendedAggregateOperations.SUM,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
};
|
||||
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
if (
|
||||
!isDefined(fieldSelection?.aggregateFieldMetadataId) ||
|
||||
!isDefined(fieldSelection?.groupByFieldMetadataIdX)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
displayDataLabel: false,
|
||||
color: 'blue' satisfies ThemeColor,
|
||||
primaryAxisGroupByFieldMetadataId:
|
||||
fieldSelection.groupByFieldMetadataIdX,
|
||||
aggregateFieldMetadataId: fieldSelection.aggregateFieldMetadataId,
|
||||
aggregateOperation: ExtendedAggregateOperations.SUM,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
};
|
||||
|
||||
|
||||
+8
-2
@@ -15,11 +15,17 @@ export const extractFieldMetadataIdsFromWidget = (
|
||||
|
||||
switch (config.__typename) {
|
||||
case 'BarChartConfiguration':
|
||||
return [
|
||||
config.aggregateFieldMetadataId,
|
||||
config.primaryAxisGroupByFieldMetadataId,
|
||||
config.secondaryAxisGroupByFieldMetadataId,
|
||||
].filter(isDefined);
|
||||
|
||||
case 'LineChartConfiguration':
|
||||
return [
|
||||
config.aggregateFieldMetadataId,
|
||||
config.groupByFieldMetadataIdX,
|
||||
config.groupByFieldMetadataIdY,
|
||||
config.primaryAxisGroupByFieldMetadataId,
|
||||
config.secondaryAxisGroupByFieldMetadataId,
|
||||
].filter(isDefined);
|
||||
|
||||
case 'PieChartConfiguration':
|
||||
|
||||
@@ -26,7 +26,7 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
|
||||
],
|
||||
};
|
||||
|
||||
case GraphType.BAR:
|
||||
case GraphType.VERTICAL_BAR:
|
||||
return {
|
||||
items: [
|
||||
{ category: 'Jan', value: 45 },
|
||||
@@ -41,6 +41,21 @@ export const getDefaultWidgetData = (graphType: GraphType) => {
|
||||
layout: 'vertical' as const,
|
||||
};
|
||||
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
return {
|
||||
items: [
|
||||
{ category: 'Jan', value: 45 },
|
||||
{ category: 'Feb', value: 52 },
|
||||
{ category: 'Mar', value: 48 },
|
||||
{ category: 'Apr', value: 61 },
|
||||
{ category: 'May', value: 55 },
|
||||
],
|
||||
indexBy: 'category',
|
||||
keys: ['value'],
|
||||
seriesLabels: { value: 'Value' },
|
||||
layout: 'horizontal' as const,
|
||||
};
|
||||
|
||||
case GraphType.LINE:
|
||||
return {
|
||||
series: [
|
||||
@@ -119,7 +134,8 @@ export const getWidgetTitle = (graphType: GraphType, index: number): string => {
|
||||
[GraphType.NUMBER]: 'Number',
|
||||
[GraphType.GAUGE]: 'Gauge',
|
||||
[GraphType.PIE]: 'Pie Chart',
|
||||
[GraphType.BAR]: 'Bar Chart',
|
||||
[GraphType.VERTICAL_BAR]: 'Vertical Bar Chart',
|
||||
[GraphType.HORIZONTAL_BAR]: 'Horizontal Bar Chart',
|
||||
[GraphType.LINE]: 'Line Chart',
|
||||
};
|
||||
|
||||
@@ -134,7 +150,8 @@ export const getWidgetSize = (graphType: GraphType) => {
|
||||
return { w: 3, h: 3 };
|
||||
case GraphType.PIE:
|
||||
return { w: 4, h: 4 };
|
||||
case GraphType.BAR:
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
return { w: 6, h: 6 };
|
||||
case GraphType.LINE:
|
||||
return { w: 6, h: 10 };
|
||||
|
||||
+12
-12
@@ -240,11 +240,11 @@ export const WithBarChart: Story = {
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
groupByFieldMetadataIdX: createdAtField.id,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: createdAtField.id,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -320,11 +320,11 @@ export const MediumWidget: Story = {
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
groupByFieldMetadataIdX: createdAtField.id,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: createdAtField.id,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -365,11 +365,11 @@ export const LargeWidget: Story = {
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
groupByFieldMetadataIdX: createdAtField.id,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: createdAtField.id,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
@@ -452,11 +452,11 @@ export const TallWidget: Story = {
|
||||
},
|
||||
configuration: {
|
||||
__typename: 'BarChartConfiguration',
|
||||
graphType: GraphType.BAR,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
aggregateFieldMetadataId: idField.id,
|
||||
groupByFieldMetadataIdX: createdAtField.id,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: createdAtField.id,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
},
|
||||
|
||||
+2
-1
@@ -100,7 +100,8 @@ export const GraphWidget = ({
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
case GraphType.BAR:
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
return <GraphWidgetBarChartRenderer widget={widget} />;
|
||||
|
||||
case GraphType.LINE:
|
||||
|
||||
+2
@@ -28,6 +28,7 @@ export const GraphWidgetBarChartRenderer = ({
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
showDataLabels,
|
||||
layout,
|
||||
loading,
|
||||
error,
|
||||
} = useGraphBarChartWidgetData({
|
||||
@@ -57,6 +58,7 @@ export const GraphWidgetBarChartRenderer = ({
|
||||
xAxisLabel={xAxisLabel}
|
||||
yAxisLabel={yAxisLabel}
|
||||
showValues={showDataLabels}
|
||||
layout={layout}
|
||||
groupMode={groupMode}
|
||||
id={widget.id}
|
||||
displayType="shortNumber"
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ type UseGraphBarChartWidgetDataResult = {
|
||||
xAxisLabel?: string;
|
||||
yAxisLabel?: string;
|
||||
showDataLabels: boolean;
|
||||
layout?: 'vertical' | 'horizontal';
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
+26
-3
@@ -1,9 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration should generate variables with composite field 1`] = `
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration Horizontal bar configuration should generate variables with secondary axis 1`] = `
|
||||
{
|
||||
"groupBy": [
|
||||
{
|
||||
"stage": true,
|
||||
},
|
||||
{
|
||||
"ownerId": true,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration Horizontal bar configuration should generate variables with single groupBy field 1`] = `
|
||||
{
|
||||
"groupBy": [
|
||||
{
|
||||
"stage": true,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration Vertical bar configuration should generate variables with composite field 1`] = `
|
||||
{
|
||||
"groupBy": [
|
||||
{
|
||||
"name": {
|
||||
"firstName": true,
|
||||
},
|
||||
@@ -12,7 +35,7 @@ exports[`generateGroupByQueryVariablesFromBarChartConfiguration should generate
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration should generate variables with single groupBy field 1`] = `
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration Vertical bar configuration should generate variables with single groupBy field 1`] = `
|
||||
{
|
||||
"groupBy": [
|
||||
{
|
||||
@@ -22,7 +45,7 @@ exports[`generateGroupByQueryVariablesFromBarChartConfiguration should generate
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration should generate variables with two groupBy fields 1`] = `
|
||||
exports[`generateGroupByQueryVariablesFromBarChartConfiguration Vertical bar configuration should generate variables with two groupBy fields 1`] = `
|
||||
{
|
||||
"groupBy": [
|
||||
{
|
||||
|
||||
+78
-32
@@ -1,7 +1,9 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
type BarChartConfiguration,
|
||||
ExtendedAggregateOperations,
|
||||
FieldMetadataType,
|
||||
GraphType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { generateGroupByQueryVariablesFromBarChartConfiguration } from '../generateGroupByQueryVariablesFromBarChartConfiguration';
|
||||
|
||||
@@ -34,52 +36,96 @@ describe('generateGroupByQueryVariablesFromBarChartConfiguration', () => {
|
||||
],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
it('should generate variables with single groupBy field', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: {
|
||||
groupByFieldMetadataIdX: 'field-1',
|
||||
groupBySubFieldNameX: null,
|
||||
} as BarChartConfiguration,
|
||||
const buildConfiguration = (
|
||||
overrides: Partial<BarChartConfiguration>,
|
||||
): BarChartConfiguration =>
|
||||
({
|
||||
__typename: 'BarChartConfiguration',
|
||||
aggregateFieldMetadataId: 'aggregate-field',
|
||||
aggregateOperation: ExtendedAggregateOperations.COUNT,
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-1',
|
||||
...overrides,
|
||||
}) as BarChartConfiguration;
|
||||
|
||||
describe('Vertical bar configuration', () => {
|
||||
it('should generate variables with single groupBy field', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-1',
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
it('should generate variables with two groupBy fields', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-1',
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
secondaryAxisGroupBySubFieldName: null,
|
||||
}),
|
||||
});
|
||||
|
||||
it('should generate variables with two groupBy fields', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: {
|
||||
groupByFieldMetadataIdX: 'field-1',
|
||||
groupBySubFieldNameX: null,
|
||||
groupByFieldMetadataIdY: 'field-2',
|
||||
groupBySubFieldNameY: null,
|
||||
} as BarChartConfiguration,
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
it('should generate variables with composite field', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-4',
|
||||
primaryAxisGroupBySubFieldName: 'firstName',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate variables with composite field', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: {
|
||||
groupByFieldMetadataIdX: 'field-4',
|
||||
groupBySubFieldNameX: 'firstName',
|
||||
} as BarChartConfiguration,
|
||||
describe('Horizontal bar configuration', () => {
|
||||
it('should generate variables with single groupBy field', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-1',
|
||||
primaryAxisGroupBySubFieldName: null,
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
it('should generate variables with secondary axis', () => {
|
||||
const result = generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
primaryAxisGroupByFieldMetadataId: 'field-1',
|
||||
secondaryAxisGroupByFieldMetadataId: 'field-2',
|
||||
}),
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw error when groupBy field X not found', () => {
|
||||
it('should throw error when primary axis field not found', () => {
|
||||
expect(() =>
|
||||
generateGroupByQueryVariablesFromBarChartConfiguration({
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
barChartConfiguration: {
|
||||
groupByFieldMetadataIdX: 'invalid-field',
|
||||
groupBySubFieldNameX: null,
|
||||
} as BarChartConfiguration,
|
||||
barChartConfiguration: buildConfiguration({
|
||||
primaryAxisGroupByFieldMetadataId: 'invalid-field',
|
||||
}),
|
||||
}),
|
||||
).toThrow('Field with id invalid-field not found in object metadata');
|
||||
});
|
||||
|
||||
+12
-10
@@ -27,20 +27,22 @@ export const areChartConfigurationFieldsValidForQuery = (
|
||||
|
||||
switch (configuration.__typename) {
|
||||
case 'BarChartConfiguration':
|
||||
case 'LineChartConfiguration': {
|
||||
const hasRequiredXFields =
|
||||
case 'LineChartConfiguration':
|
||||
return (
|
||||
fieldExists(
|
||||
configuration.aggregateFieldMetadataId,
|
||||
objectMetadataItem,
|
||||
) &&
|
||||
fieldExists(configuration.groupByFieldMetadataIdX, objectMetadataItem);
|
||||
|
||||
const hasValidYField =
|
||||
!isDefined(configuration.groupByFieldMetadataIdY) ||
|
||||
fieldExists(configuration.groupByFieldMetadataIdY, objectMetadataItem);
|
||||
|
||||
return hasRequiredXFields && hasValidYField;
|
||||
}
|
||||
fieldExists(
|
||||
configuration.primaryAxisGroupByFieldMetadataId,
|
||||
objectMetadataItem,
|
||||
) &&
|
||||
(!isDefined(configuration.secondaryAxisGroupByFieldMetadataId) ||
|
||||
fieldExists(
|
||||
configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
objectMetadataItem,
|
||||
))
|
||||
);
|
||||
|
||||
case 'PieChartConfiguration':
|
||||
return (
|
||||
|
||||
+27
-12
@@ -25,17 +25,29 @@ export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
barChartConfiguration: BarChartConfiguration;
|
||||
}) => {
|
||||
const groupByFieldXId =
|
||||
barChartConfiguration.primaryAxisGroupByFieldMetadataId;
|
||||
|
||||
const groupByFieldYId =
|
||||
barChartConfiguration.secondaryAxisGroupByFieldMetadataId;
|
||||
|
||||
const groupBySubFieldNameX =
|
||||
barChartConfiguration.primaryAxisGroupBySubFieldName ?? undefined;
|
||||
|
||||
const groupBySubFieldNameY =
|
||||
barChartConfiguration.secondaryAxisGroupBySubFieldName ?? undefined;
|
||||
|
||||
const groupByFieldX = objectMetadataItem.fields.find(
|
||||
(field) => field.id === barChartConfiguration.groupByFieldMetadataIdX,
|
||||
(field) => field.id === groupByFieldXId,
|
||||
);
|
||||
|
||||
const groupByFieldY = objectMetadataItem.fields.find(
|
||||
(field) => field.id === barChartConfiguration.groupByFieldMetadataIdY,
|
||||
);
|
||||
const groupByFieldY = isDefined(groupByFieldYId)
|
||||
? objectMetadataItem.fields.find((field) => field.id === groupByFieldYId)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(groupByFieldX)) {
|
||||
if (!isDefined(groupByFieldX) || !isDefined(groupByFieldXId)) {
|
||||
throw new Error(
|
||||
`Field with id ${barChartConfiguration.groupByFieldMetadataIdX} not found in object metadata`,
|
||||
`Field with id ${groupByFieldXId} not found in object metadata`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,7 +58,7 @@ export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
field: groupByFieldX,
|
||||
subFieldName: barChartConfiguration.groupBySubFieldNameX,
|
||||
subFieldName: groupBySubFieldNameX,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -54,7 +66,7 @@ export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
|
||||
groupBy.push(
|
||||
buildGroupByFieldObject({
|
||||
field: groupByFieldY,
|
||||
subFieldName: barChartConfiguration.groupBySubFieldNameY,
|
||||
subFieldName: groupBySubFieldNameY,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -62,18 +74,21 @@ export const generateGroupByQueryVariablesFromBarChartConfiguration = ({
|
||||
const orderBy: Array<Record<string, string>> = [];
|
||||
|
||||
// TODO: Add orderBy back in when the backend is ready
|
||||
// if (isDefined(barChartConfiguration.orderByX)) {
|
||||
// if (isDefined(barChartConfiguration.primaryAxisOrderBy)) {
|
||||
// orderBy.push({
|
||||
// [groupByFieldX.name]: mapOrderByToDirection(
|
||||
// barChartConfiguration.orderByX,
|
||||
// barChartConfiguration.primaryAxisOrderBy!,
|
||||
// ),
|
||||
// });
|
||||
// }
|
||||
|
||||
// if (isDefined(groupByFieldY) && isDefined(barChartConfiguration.orderByY)) {
|
||||
// if (
|
||||
// isDefined(groupByFieldY) &&
|
||||
// isDefined(barChartConfiguration.secondaryAxisOrderBy)
|
||||
// ) {
|
||||
// orderBy.push({
|
||||
// [groupByFieldY.name]: mapOrderByToDirection(
|
||||
// barChartConfiguration.orderByY,
|
||||
// barChartConfiguration.secondaryAxisOrderBy!,
|
||||
// ),
|
||||
// });
|
||||
// }
|
||||
|
||||
+35
-7
@@ -9,6 +9,7 @@ import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey';
|
||||
import { transformOneDimensionalGroupByToBarChartData } from '@/page-layout/widgets/graph/utils/transformOneDimensionalGroupByToBarChartData';
|
||||
import { transformTwoDimensionalGroupByToBarChartData } from '@/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToBarChartData';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { GraphType } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
AxisNameDisplay,
|
||||
type BarChartConfiguration,
|
||||
@@ -29,6 +30,7 @@ type TransformGroupByDataToBarChartDataResult = {
|
||||
xAxisLabel?: string;
|
||||
yAxisLabel?: string;
|
||||
showDataLabels: boolean;
|
||||
layout?: 'vertical' | 'horizontal';
|
||||
};
|
||||
|
||||
const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = {
|
||||
@@ -39,6 +41,7 @@ const EMPTY_BAR_CHART_RESULT: TransformGroupByDataToBarChartDataResult = {
|
||||
xAxisLabel: undefined,
|
||||
yAxisLabel: undefined,
|
||||
showDataLabels: false,
|
||||
layout: 'vertical',
|
||||
};
|
||||
|
||||
export const transformGroupByDataToBarChartData = ({
|
||||
@@ -53,13 +56,17 @@ export const transformGroupByDataToBarChartData = ({
|
||||
|
||||
const groupByFieldX = objectMetadataItem.fields.find(
|
||||
(field: FieldMetadataItem) =>
|
||||
field.id === configuration.groupByFieldMetadataIdX,
|
||||
field.id === configuration.primaryAxisGroupByFieldMetadataId,
|
||||
);
|
||||
|
||||
const groupByFieldY = objectMetadataItem.fields.find(
|
||||
(field: FieldMetadataItem) =>
|
||||
field.id === configuration.groupByFieldMetadataIdY,
|
||||
);
|
||||
const groupByFieldY = isDefined(
|
||||
configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
)
|
||||
? objectMetadataItem.fields.find(
|
||||
(field: FieldMetadataItem) =>
|
||||
field.id === configuration.secondaryAxisGroupByFieldMetadataId,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const aggregateField = objectMetadataItem.fields.find(
|
||||
(field: FieldMetadataItem) =>
|
||||
@@ -67,12 +74,21 @@ export const transformGroupByDataToBarChartData = ({
|
||||
);
|
||||
|
||||
if (!isDefined(groupByFieldX) || !isDefined(aggregateField)) {
|
||||
return EMPTY_BAR_CHART_RESULT;
|
||||
return {
|
||||
...EMPTY_BAR_CHART_RESULT,
|
||||
layout:
|
||||
configuration.graphType === GraphType.HORIZONTAL_BAR
|
||||
? 'horizontal'
|
||||
: 'vertical',
|
||||
};
|
||||
}
|
||||
|
||||
const primaryAxisSubFieldName =
|
||||
configuration.primaryAxisGroupBySubFieldName ?? undefined;
|
||||
|
||||
const indexByKey = getFieldKey({
|
||||
field: groupByFieldX,
|
||||
subFieldName: configuration.groupBySubFieldNameX,
|
||||
subFieldName: primaryAxisSubFieldName,
|
||||
});
|
||||
|
||||
const queryName = getGroupByQueryName(objectMetadataItem);
|
||||
@@ -82,6 +98,10 @@ export const transformGroupByDataToBarChartData = ({
|
||||
return {
|
||||
...EMPTY_BAR_CHART_RESULT,
|
||||
indexBy: indexByKey,
|
||||
layout:
|
||||
configuration.graphType === GraphType.HORIZONTAL_BAR
|
||||
? 'horizontal'
|
||||
: 'vertical',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,6 +130,7 @@ export const transformGroupByDataToBarChartData = ({
|
||||
configuration,
|
||||
aggregateOperation,
|
||||
objectMetadataItem,
|
||||
primaryAxisSubFieldName,
|
||||
})
|
||||
: transformOneDimensionalGroupByToBarChartData({
|
||||
rawResults,
|
||||
@@ -118,12 +139,19 @@ export const transformGroupByDataToBarChartData = ({
|
||||
configuration,
|
||||
aggregateOperation,
|
||||
objectMetadataItem,
|
||||
primaryAxisSubFieldName,
|
||||
});
|
||||
|
||||
const layout =
|
||||
configuration.graphType === GraphType.HORIZONTAL_BAR
|
||||
? 'horizontal'
|
||||
: 'vertical';
|
||||
|
||||
return {
|
||||
...baseResult,
|
||||
xAxisLabel,
|
||||
yAxisLabel,
|
||||
showDataLabels,
|
||||
layout,
|
||||
};
|
||||
};
|
||||
|
||||
+5
-2
@@ -20,6 +20,7 @@ type TransformOneDimensionalGroupByToBarChartDataParams = {
|
||||
configuration: BarChartConfiguration;
|
||||
aggregateOperation: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
primaryAxisSubFieldName?: string | null;
|
||||
};
|
||||
|
||||
type TransformOneDimensionalGroupByToBarChartDataResult = {
|
||||
@@ -36,10 +37,11 @@ export const transformOneDimensionalGroupByToBarChartData = ({
|
||||
configuration,
|
||||
aggregateOperation,
|
||||
objectMetadataItem,
|
||||
primaryAxisSubFieldName,
|
||||
}: TransformOneDimensionalGroupByToBarChartDataParams): TransformOneDimensionalGroupByToBarChartDataResult => {
|
||||
const indexByKey = getFieldKey({
|
||||
field: groupByFieldX,
|
||||
subFieldName: configuration.groupBySubFieldNameX,
|
||||
subFieldName: primaryAxisSubFieldName ?? undefined,
|
||||
});
|
||||
|
||||
// TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600)
|
||||
@@ -52,7 +54,8 @@ export const transformOneDimensionalGroupByToBarChartData = ({
|
||||
? formatDimensionValue({
|
||||
value: dimensionValues[0],
|
||||
fieldMetadata: groupByFieldX,
|
||||
subFieldName: configuration.groupBySubFieldNameX ?? undefined,
|
||||
subFieldName:
|
||||
configuration.primaryAxisGroupBySubFieldName ?? undefined,
|
||||
})
|
||||
: '';
|
||||
|
||||
|
||||
+5
-3
@@ -19,6 +19,7 @@ type TransformTwoDimensionalGroupByToBarChartDataParams = {
|
||||
configuration: BarChartConfiguration;
|
||||
aggregateOperation: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
primaryAxisSubFieldName?: string | null;
|
||||
};
|
||||
|
||||
type TransformTwoDimensionalGroupByToBarChartDataResult = {
|
||||
@@ -36,10 +37,11 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
|
||||
configuration,
|
||||
aggregateOperation,
|
||||
objectMetadataItem,
|
||||
primaryAxisSubFieldName,
|
||||
}: TransformTwoDimensionalGroupByToBarChartDataParams): TransformTwoDimensionalGroupByToBarChartDataResult => {
|
||||
const indexByKey = getFieldKey({
|
||||
field: groupByFieldX,
|
||||
subFieldName: configuration.groupBySubFieldNameX,
|
||||
subFieldName: primaryAxisSubFieldName ?? undefined,
|
||||
});
|
||||
|
||||
const dataMap = new Map<string, BarChartDataItem>();
|
||||
@@ -53,12 +55,12 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
|
||||
const xValue = formatDimensionValue({
|
||||
value: dimensionValues[0],
|
||||
fieldMetadata: groupByFieldX,
|
||||
subFieldName: configuration.groupBySubFieldNameX ?? undefined,
|
||||
subFieldName: configuration.primaryAxisGroupBySubFieldName ?? undefined,
|
||||
});
|
||||
const yValue = formatDimensionValue({
|
||||
value: dimensionValues[1],
|
||||
fieldMetadata: groupByFieldY,
|
||||
subFieldName: configuration.groupBySubFieldNameY ?? undefined,
|
||||
subFieldName: configuration.secondaryAxisGroupBySubFieldName ?? undefined,
|
||||
});
|
||||
|
||||
// TODO: Add a limit to the query instead of checking here (issue: twentyhq/core-team-issues#1600)
|
||||
|
||||
+9
-8
@@ -3,6 +3,7 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsIn,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
@@ -24,9 +25,9 @@ import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.
|
||||
@ObjectType('BarChartConfiguration')
|
||||
export class BarChartConfigurationDTO {
|
||||
@Field(() => GraphType)
|
||||
@IsEnum(GraphType)
|
||||
@IsIn([GraphType.VERTICAL_BAR, GraphType.HORIZONTAL_BAR])
|
||||
@IsNotEmpty()
|
||||
graphType: GraphType.BAR;
|
||||
graphType: GraphType.VERTICAL_BAR | GraphType.HORIZONTAL_BAR;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@@ -41,32 +42,32 @@ export class BarChartConfigurationDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
groupByFieldMetadataIdX: string;
|
||||
primaryAxisGroupByFieldMetadataId: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
groupBySubFieldNameX?: string;
|
||||
primaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
orderByX?: GraphOrderBy;
|
||||
primaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
groupByFieldMetadataIdY?: string;
|
||||
secondaryAxisGroupByFieldMetadataId?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
groupBySubFieldNameY?: string;
|
||||
secondaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
orderByY?: GraphOrderBy;
|
||||
secondaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
|
||||
+6
-6
@@ -40,12 +40,12 @@ export class LineChartConfigurationDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
groupByFieldMetadataIdX: string;
|
||||
primaryAxisGroupByFieldMetadataId: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
groupBySubFieldNameX?: string;
|
||||
primaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => GraphOrderBy, {
|
||||
nullable: true,
|
||||
@@ -53,22 +53,22 @@ export class LineChartConfigurationDTO {
|
||||
})
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
orderByX?: GraphOrderBy;
|
||||
primaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
groupByFieldMetadataIdY?: string;
|
||||
secondaryAxisGroupByFieldMetadataId?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
groupBySubFieldNameY?: string;
|
||||
secondaryAxisGroupBySubFieldName?: string;
|
||||
|
||||
@Field(() => GraphOrderBy, { nullable: true })
|
||||
@IsEnum(GraphOrderBy)
|
||||
@IsOptional()
|
||||
orderByY?: GraphOrderBy;
|
||||
secondaryAxisOrderBy?: GraphOrderBy;
|
||||
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
|
||||
+2
-1
@@ -30,7 +30,8 @@ export const WidgetConfiguration = createUnionType({
|
||||
configuration.configurationType === WidgetConfigurationType.CHART_CONFIG
|
||||
) {
|
||||
switch (configuration.graphType) {
|
||||
case GraphType.BAR:
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR:
|
||||
return BarChartConfigurationDTO;
|
||||
case GraphType.LINE:
|
||||
return LineChartConfigurationDTO;
|
||||
|
||||
@@ -4,7 +4,8 @@ export enum GraphType {
|
||||
NUMBER = 'NUMBER',
|
||||
GAUGE = 'GAUGE',
|
||||
PIE = 'PIE',
|
||||
BAR = 'BAR',
|
||||
VERTICAL_BAR = 'VERTICAL_BAR',
|
||||
HORIZONTAL_BAR = 'HORIZONTAL_BAR',
|
||||
LINE = 'LINE',
|
||||
}
|
||||
|
||||
|
||||
+45
-13
@@ -1,12 +1,15 @@
|
||||
import {
|
||||
INVALID_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_IFRAME_CONFIG_BAD_URL,
|
||||
INVALID_IFRAME_CONFIG_EMPTY_URL,
|
||||
INVALID_IFRAME_CONFIG_MISSING_URL,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
TEST_BAR_CHART_CONFIG,
|
||||
TEST_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
TEST_GAUGE_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
@@ -99,32 +102,61 @@ describe('validateAndTransformWidgetConfiguration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('BAR graph', () => {
|
||||
it('should validate full bar graph configuration', () => {
|
||||
describe('VERTICAL_BAR graph', () => {
|
||||
it('should validate full vertical bar graph configuration', () => {
|
||||
const result = validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
TEST_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject(TEST_BAR_CHART_CONFIG);
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal bar graph configuration', () => {
|
||||
it('should validate minimal vertical bar graph configuration', () => {
|
||||
const result = validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
TEST_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject(TEST_BAR_CHART_CONFIG_MINIMAL);
|
||||
expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial bar graph configuration with missing required fields', () => {
|
||||
it('should throw error for partial vertical bar graph configuration with missing required fields', () => {
|
||||
expect(() =>
|
||||
validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
INVALID_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
),
|
||||
).toThrow(/groupByFieldMetadataIdX/);
|
||||
).toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HORIZONTAL_BAR graph', () => {
|
||||
it('should validate full horizontal bar graph configuration', () => {
|
||||
const result = validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG);
|
||||
});
|
||||
|
||||
it('should validate minimal horizontal bar graph configuration', () => {
|
||||
const result = validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL);
|
||||
});
|
||||
|
||||
it('should throw error for partial horizontal bar graph configuration with missing required fields', () => {
|
||||
expect(() =>
|
||||
validateAndTransformWidgetConfiguration(
|
||||
WidgetType.GRAPH,
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
),
|
||||
).toThrow(/primaryAxisGroupByFieldMetadataId/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ const validateGraphConfiguration = (
|
||||
}
|
||||
|
||||
switch (graphType) {
|
||||
case GraphType.BAR: {
|
||||
case GraphType.VERTICAL_BAR:
|
||||
case GraphType.HORIZONTAL_BAR: {
|
||||
const instance = plainToInstance(BarChartConfigurationDTO, configuration);
|
||||
|
||||
const errors = validateSync(instance, {
|
||||
|
||||
+20
-19
@@ -2,6 +2,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { AxisNameDisplay } from 'src/engine/core-modules/page-layout/enums/axis-name-display.enum';
|
||||
import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.enum';
|
||||
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PAGE_LAYOUT_TAB_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-tab-seeds.constant';
|
||||
@@ -142,8 +143,8 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
graphType: 'LINE',
|
||||
aggregateFieldMetadataId: opportunityAmountFieldId,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
groupByFieldMetadataIdX: opportunityCloseDateFieldId,
|
||||
orderByX: 'FIELD_ASC',
|
||||
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
|
||||
primaryAxisOrderBy: 'FIELD_ASC',
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
@@ -167,12 +168,12 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
isDefined(opportunityCloseDateFieldId) &&
|
||||
isDefined(opportunityStageFieldId)
|
||||
? {
|
||||
graphType: 'BAR',
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: opportunityAmountFieldId,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
groupByFieldMetadataIdX: opportunityCloseDateFieldId,
|
||||
groupByFieldMetadataIdY: opportunityStageFieldId,
|
||||
orderByX: 'FIELD_ASC',
|
||||
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
|
||||
secondaryAxisGroupByFieldMetadataId: opportunityStageFieldId,
|
||||
primaryAxisOrderBy: 'FIELD_ASC',
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
@@ -196,11 +197,11 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
configuration:
|
||||
isDefined(rocketIdFieldId) && isDefined(rocketCreatedAtFieldId)
|
||||
? {
|
||||
graphType: 'BAR',
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: rocketIdFieldId,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: rocketCreatedAtFieldId,
|
||||
orderByX: 'FIELD_ASC',
|
||||
primaryAxisGroupByFieldMetadataId: rocketCreatedAtFieldId,
|
||||
primaryAxisOrderBy: 'FIELD_ASC',
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
@@ -271,8 +272,8 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
graphType: 'LINE',
|
||||
aggregateFieldMetadataId: companyIdFieldId,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: companyCreatedAtFieldId,
|
||||
orderByX: 'FIELD_ASC',
|
||||
primaryAxisGroupByFieldMetadataId: companyCreatedAtFieldId,
|
||||
primaryAxisOrderBy: 'FIELD_ASC',
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
@@ -296,13 +297,13 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
isDefined(companyEmployeesFieldId) &&
|
||||
isDefined(companyAddressFieldId)
|
||||
? {
|
||||
graphType: 'BAR',
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: companyIdFieldId,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: companyEmployeesFieldId,
|
||||
groupByFieldMetadataIdY: companyAddressFieldId,
|
||||
groupBySubFieldNameY: 'addressCity',
|
||||
orderByX: 'FIELD_ASC',
|
||||
primaryAxisGroupByFieldMetadataId: companyEmployeesFieldId,
|
||||
secondaryAxisGroupByFieldMetadataId: companyAddressFieldId,
|
||||
secondaryAxisGroupBySubFieldName: 'addressCity',
|
||||
primaryAxisOrderBy: 'FIELD_ASC',
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
@@ -463,11 +464,11 @@ export const getPageLayoutWidgetDataSeeds = (
|
||||
configuration:
|
||||
isDefined(personIdFieldId) && isDefined(personCityFieldId)
|
||||
? {
|
||||
graphType: 'BAR',
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: personIdFieldId,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: personCityFieldId,
|
||||
orderByX: 'VALUE_DESC',
|
||||
primaryAxisGroupByFieldMetadataId: personCityFieldId,
|
||||
primaryAxisOrderBy: 'VALUE_DESC',
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
displayDataLabel: false,
|
||||
}
|
||||
|
||||
+12
-8
@@ -26,10 +26,12 @@ export const PAGE_LAYOUT_WIDGET_CONFIGURATION_FIELDS = `
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
@@ -43,10 +45,12 @@ export const PAGE_LAYOUT_WIDGET_CONFIGURATION_FIELDS = `
|
||||
graphType
|
||||
aggregateFieldMetadataId
|
||||
aggregateOperation
|
||||
groupByFieldMetadataIdX
|
||||
orderByX
|
||||
groupByFieldMetadataIdY
|
||||
orderByY
|
||||
primaryAxisGroupByFieldMetadataId
|
||||
primaryAxisGroupBySubFieldName
|
||||
primaryAxisOrderBy
|
||||
secondaryAxisGroupByFieldMetadataId
|
||||
secondaryAxisGroupBySubFieldName
|
||||
secondaryAxisOrderBy
|
||||
omitNullValues
|
||||
axisNameDisplay
|
||||
displayDataLabel
|
||||
|
||||
+67
-24
@@ -33,12 +33,12 @@ export const TEST_NUMBER_CHART_CONFIG_MINIMAL = {
|
||||
displayDataLabel: false,
|
||||
};
|
||||
|
||||
export const TEST_BAR_CHART_CONFIG = {
|
||||
graphType: GraphType.BAR,
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG = {
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
groupByFieldMetadataIdX: TEST_FIELD_METADATA_ID_2,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
displayDataLabel: true,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
color: 'red',
|
||||
@@ -48,12 +48,37 @@ export const TEST_BAR_CHART_CONFIG = {
|
||||
rangeMax: 100000,
|
||||
};
|
||||
|
||||
export const TEST_BAR_CHART_CONFIG_MINIMAL = {
|
||||
graphType: GraphType.BAR,
|
||||
export const TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL = {
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
groupByFieldMetadataIdX: TEST_FIELD_METADATA_ID_2,
|
||||
orderByX: GraphOrderBy.VALUE_DESC,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG = {
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
displayDataLabel: true,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
color: 'blue',
|
||||
description: 'Horizontal revenue breakdown',
|
||||
omitNullValues: true,
|
||||
rangeMin: 0,
|
||||
rangeMax: 100000,
|
||||
};
|
||||
|
||||
export const TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL = {
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_DESC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
@@ -62,10 +87,10 @@ export const TEST_LINE_CHART_CONFIG = {
|
||||
graphType: GraphType.LINE,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.AVG,
|
||||
groupByFieldMetadataIdX: TEST_FIELD_METADATA_ID_2,
|
||||
orderByX: GraphOrderBy.FIELD_ASC,
|
||||
groupByFieldMetadataIdY: TEST_FIELD_METADATA_ID_3,
|
||||
orderByY: GraphOrderBy.FIELD_DESC,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
|
||||
secondaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_3,
|
||||
secondaryAxisOrderBy: GraphOrderBy.FIELD_DESC,
|
||||
displayDataLabel: true,
|
||||
axisNameDisplay: AxisNameDisplay.BOTH,
|
||||
color: 'cyan',
|
||||
@@ -79,8 +104,8 @@ export const TEST_LINE_CHART_CONFIG_MINIMAL = {
|
||||
graphType: GraphType.LINE,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.MAX,
|
||||
groupByFieldMetadataIdX: TEST_FIELD_METADATA_ID_2,
|
||||
orderByX: GraphOrderBy.VALUE_ASC,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: GraphOrderBy.VALUE_ASC,
|
||||
displayDataLabel: false,
|
||||
axisNameDisplay: AxisNameDisplay.NONE,
|
||||
};
|
||||
@@ -164,18 +189,32 @@ export const INVALID_NUMBER_CHART_CONFIG_INVALID_OPERATION = {
|
||||
aggregateOperation: 'INVALID_OP' as any,
|
||||
};
|
||||
|
||||
export const INVALID_BAR_CHART_CONFIG_MISSING_GROUP_BY = {
|
||||
graphType: GraphType.BAR,
|
||||
export const INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY = {
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
};
|
||||
|
||||
export const INVALID_BAR_CHART_CONFIG_BAD_ORDER_BY = {
|
||||
graphType: GraphType.BAR,
|
||||
export const INVALID_VERTICAL_BAR_CHART_CONFIG_BAD_ORDER_BY = {
|
||||
graphType: GraphType.VERTICAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
groupByFieldMetadataIdX: TEST_FIELD_METADATA_ID_2,
|
||||
orderByX: 'INVALID_ORDER' as any,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: 'INVALID_ORDER' as any,
|
||||
};
|
||||
|
||||
export const INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY = {
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
};
|
||||
|
||||
export const INVALID_HORIZONTAL_BAR_CHART_CONFIG_BAD_ORDER_BY = {
|
||||
graphType: GraphType.HORIZONTAL_BAR,
|
||||
aggregateFieldMetadataId: TEST_FIELD_METADATA_ID_1,
|
||||
aggregateOperation: AggregateOperations.SUM,
|
||||
primaryAxisGroupByFieldMetadataId: TEST_FIELD_METADATA_ID_2,
|
||||
primaryAxisOrderBy: 'INVALID_ORDER' as any,
|
||||
};
|
||||
|
||||
export const CONFIG_TYPE_MISMATCH_IFRAME_WITH_GRAPH = {
|
||||
@@ -189,7 +228,8 @@ export const CONFIG_TYPE_MISMATCH_GRAPH_WITH_IFRAME = {
|
||||
|
||||
export const ALL_VALID_GRAPH_CONFIGS = [
|
||||
TEST_NUMBER_CHART_CONFIG,
|
||||
TEST_BAR_CHART_CONFIG,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG,
|
||||
TEST_LINE_CHART_CONFIG,
|
||||
TEST_PIE_CHART_CONFIG,
|
||||
TEST_GAUGE_CHART_CONFIG,
|
||||
@@ -197,7 +237,8 @@ export const ALL_VALID_GRAPH_CONFIGS = [
|
||||
|
||||
export const ALL_MINIMAL_GRAPH_CONFIGS = [
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
TEST_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_LINE_CHART_CONFIG_MINIMAL,
|
||||
TEST_PIE_CHART_CONFIG_MINIMAL,
|
||||
TEST_GAUGE_CHART_CONFIG_MINIMAL,
|
||||
@@ -210,8 +251,10 @@ export const ALL_INVALID_CONFIGS = [
|
||||
INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS,
|
||||
INVALID_NUMBER_CHART_CONFIG_BAD_UUID,
|
||||
INVALID_NUMBER_CHART_CONFIG_INVALID_OPERATION,
|
||||
INVALID_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_BAR_CHART_CONFIG_BAD_ORDER_BY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_VERTICAL_BAR_CHART_CONFIG_BAD_ORDER_BY,
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY,
|
||||
INVALID_HORIZONTAL_BAR_CHART_CONFIG_BAD_ORDER_BY,
|
||||
];
|
||||
|
||||
export function getValidConfigForWidgetType(widgetType: string): any {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
TEST_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
TEST_IFRAME_CONFIG,
|
||||
TEST_IFRAME_CONFIG_ALTERNATIVE,
|
||||
TEST_NUMBER_CHART_CONFIG_MINIMAL,
|
||||
@@ -215,7 +215,7 @@ describe('Page Layout Update With Tabs And Widgets Integration', () => {
|
||||
rowSpan: 2,
|
||||
columnSpan: 3,
|
||||
},
|
||||
configuration: TEST_BAR_CHART_CONFIG_MINIMAL,
|
||||
configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL,
|
||||
},
|
||||
{
|
||||
id: newWidgetId,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { IconChartBar } from '@ui/display/icon/components/TablerIcons';
|
||||
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
|
||||
|
||||
const StyledRotatedIconWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
transform: rotate(90deg);
|
||||
`;
|
||||
|
||||
type IconChartBarHorizontalProps = Pick<
|
||||
IconComponentProps,
|
||||
'size' | 'stroke' | 'color'
|
||||
>;
|
||||
|
||||
export const IconChartBarHorizontal = (props: IconChartBarHorizontalProps) => {
|
||||
const theme = useTheme();
|
||||
const size = props.size ?? theme.icon.size.md;
|
||||
const stroke = props.stroke ?? theme.icon.stroke.sm;
|
||||
|
||||
return (
|
||||
<StyledRotatedIconWrapper>
|
||||
<IconChartBar size={size} stroke={stroke} color={props.color} />
|
||||
</StyledRotatedIconWrapper>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ export type {
|
||||
} from './color/components/ColorSample';
|
||||
export { ColorSample } from './color/components/ColorSample';
|
||||
export { IconAddressBook } from './icon/components/IconAddressBook';
|
||||
export { IconChartBarHorizontal } from './icon/components/IconChartBarHorizontal';
|
||||
export { IconGmail } from './icon/components/IconGmail';
|
||||
export { IconGoogle } from './icon/components/IconGoogle';
|
||||
export { IconGoogleCalendar } from './icon/components/IconGoogleCalendar';
|
||||
|
||||
Reference in New Issue
Block a user