From 9a1c05fd8d01509762d5541ba028b3e189d41b2f Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Fri, 19 Sep 2025 18:32:45 +0530 Subject: [PATCH 1/9] feat: implement a functionality of hierarchy data items --- .../DataSeriesFormContent.jsx | 398 +++++++++++------- .../DashboardEditor/DashboardEditor.story.jsx | 67 +++ .../DashboardEditor/editorUtils.jsx | 12 + 3 files changed, 324 insertions(+), 153 deletions(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index ae70eea26f..15ebbf5cfc 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useCallback, useRef } from 'react'; import PropTypes from 'prop-types'; -import { Edit, MisuseOutline } from '@carbon/react/icons'; +import { Add, Edit, MisuseOutline } from '@carbon/react/icons'; import { omit, isEmpty } from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; import hash from 'object-hash'; @@ -108,6 +108,7 @@ const propTypes = { closeMenuText: PropTypes.string, incrementNumberText: PropTypes.string, decrementNumberText: PropTypes.string, + hierarchyDataItemSectionTitle: PropTypes.string, }), translateWithId: PropTypes.func.isRequired, actions: DashboardEditorActionsPropTypes, @@ -147,6 +148,7 @@ const defaultProps = { closeMenuText: 'Close menu', incrementNumberText: 'Increment number', decrementNumberText: 'Decrement number', + hierarchyDataItemSectionTitle: 'Hierarchy Data Item', }, getValidDataItems: null, dataItems: [], @@ -257,7 +259,11 @@ const DataSeriesFormItem = ({ translateWithId, actions, }) => { - const { onEditDataItem } = actions; + const { + onEditDataItem, + onAddHierarchyDataItems, + dataSeriesFormActions: { hasHierarchyDataItemsEnabled }, + } = actions; const mergedI18n = useMemo(() => ({ ...defaultProps.i18n, ...i18n }), [i18n]); const [showEditor, setShowEditor] = useState(false); @@ -274,6 +280,12 @@ const DataSeriesFormItem = ({ ? cardConfig?.content?.series : cardConfig?.content?.attributes; + // determine if hierarchy data items are available + const isHierarchyDataItemsEnabled = useMemo( + () => hasHierarchyDataItemsEnabled && hasHierarchyDataItemsEnabled(cardConfig), + [cardConfig, hasHierarchyDataItemsEnabled] + ); + const removedItemsCountRef = useRef(0); const validDataItems = useMemo( @@ -328,6 +340,42 @@ const DataSeriesFormItem = ({ [cardConfig, dataSection, onChange, setSelectedDataItems, validDataItems] ); + const handleHierarchyDataItemChange = useCallback( + (selectedItem) => { + if (selectedItem && !isEmpty(selectedItem.dataItemId)) { + const selectedItems = [ + ...dataSection, + { + ...selectedItem, + // create a unique dataSourceId if it's going into attributes + // if it's going into the groupBy section then just use the dataItem ID + dataSourceId: + selectedItem?.destination === 'groupBy' + ? selectedItem.dataItemId + : `${selectedItem.dataItemId}_${uuidv4()}`, + }, + ]; + // need to remove the category if the card is a stacked timeseries bar + const card = + cardConfig.content.type === BAR_CHART_TYPES.STACKED && + cardConfig.content.timeDataSourceId && + selectedItems.length > 1 + ? omit(cardConfig, 'content.categoryDataSourceId') + : cardConfig; + const newCard = handleDataSeriesChange( + selectedItems, + card, + setEditDataSeries, + undefined, + removedItemsCountRef + ); + setSelectedDataItems(selectedItems.map(({ dataSourceId }) => dataSourceId)); + onChange(newCard); + } + }, + [cardConfig, dataSection, onChange, setSelectedDataItems] + ); + const handleEditButton = useCallback( async (dataItem, i) => { const dataItemWithMetaData = validDataItems?.find( @@ -387,171 +435,215 @@ const DataSeriesFormItem = ({ [cardConfig, dataSection, onChange, removedDataItems, setSelectedDataItems] ); - const dataItemListItems = useMemo( - () => - dataSection?.map((dataItem, i) => { - const colorIndex = (i + removedItemsCountRef.current) % DATAITEM_COLORS_OPTIONS.length; - const iconColorOption = dataItem.color || DATAITEM_COLORS_OPTIONS[colorIndex]; - return { - id: dataItem.dataSourceId, - content: { - value: dataItem.label || dataItem.dataItemId, - icon: - cardConfig.type === CARD_TYPES.TIMESERIES || cardConfig.type === CARD_TYPES.BAR ? ( -
- ) : null, - rowActions: () => [ - + + } + title="" + items={hierarchyDataItemListItems} /> - )} -
- - } - title="" - items={dataItemListItems} - /> + + )} - ) : null; + ); }; DataSeriesFormItem.defaultProps = defaultProps; DataSeriesFormItem.propTypes = propTypes; diff --git a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx index ac44ee748f..7c3659c8fb 100644 --- a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx +++ b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx @@ -1519,3 +1519,70 @@ export const withGetDefaultCard = () => { }; withGetDefaultCard.storyName = 'With get default card'; + +export const withHierarchyDataItems = () => { + const hierarchyDataItems = object('hierarchyDataItems', { + dataItemId: 'speed', + // eslint-disable-next-line no-plusplus + label: `speed:Device`, + resourceData: { + type: 'DEVICE', + uuid: '12345', + deviceTypeUUId: '7890', + }, + }); + const actions = { + ...commonActions, + onAddHierarchyDataItems: (cb) => cb(hierarchyDataItems), + dataSeriesFormActions: { + ...commonActions.dataSeriesFormActions, + hasHierarchyDataItemsEnabled: (card) => + card.type === CARD_TYPES.TIMESERIES || card.type === CARD_TYPES.BAR, + }, + }; + + return ( + mockDataItems} + dataItems={mockDataItems} + availableImages={images} + i18n={{ + headerEditTitleButton: 'Edit title updated', + }} + onAddImage={action('onAddImage')} + onImport={action('onImport')} + onExport={action('onExport')} + onDelete={action('onDelete')} + onCancel={action('onCancel')} + onSubmit={action('onSubmit')} + actions={actions} + onImageDelete={action('onImageDelete')} + onLayoutChange={action('onLayoutChange')} + isSubmitDisabled={boolean('isSubmitDisabled', false)} + isSubmitLoading={boolean('isSubmitLoading', false)} + availableDimensions={{ + deviceid: ['73000', '73001', '73002'], + manufacturer: ['rentech', 'GHI Industries'], + }} + supportedCardTypes={array('supportedCardTypes', [ + 'TIMESERIES', + 'SIMPLE_BAR', + 'GROUPED_BAR', + 'STACKED_BAR', + 'VALUE', + 'IMAGE', + 'TABLE', + 'CUSTOM', + ])} + headerBreadcrumbs={[ + Dashboard library, + Favorites, + ]} + isLoading={boolean('isLoading', false)} + onCardSelect={action('onCardSelect')} + /> + ); +}; + +withHierarchyDataItems.storyName = 'with hierarchy data items'; diff --git a/packages/react/src/components/DashboardEditor/editorUtils.jsx b/packages/react/src/components/DashboardEditor/editorUtils.jsx index d6e3606aad..eecdf05129 100644 --- a/packages/react/src/components/DashboardEditor/editorUtils.jsx +++ b/packages/react/src/components/DashboardEditor/editorUtils.jsx @@ -692,6 +692,11 @@ export const DashboardEditorActionsPropTypes = PropTypes.shape({ ] */ onEditDataItem: PropTypes.func, + /** Call back function for on click of add hierarchy data items button, returns a selcted dataSource + * onAddHierarchyDataItems(handleHierarchyDataItemChange: callback function to handle hierarchy data items change) + * return: void + */ + onAddHierarchyDataItems: PropTypes.func, /** Form actions for dataSeries modal */ dataSeriesFormActions: PropTypes.shape({ /** callback function to determine aggregation dropdown visibility @@ -709,6 +714,11 @@ export const DashboardEditorActionsPropTypes = PropTypes.shape({ * return {boolean} : true or false */ hasDataFilterDropdown: PropTypes.func, + /** callback function to determine hierarchyDataItems dropdown visibility + * hasHierarchyDataItemsEnabled(cardProps: card properties) + * return {boolean} : true or false + */ + hasHierarchyDataItemsEnabled: PropTypes.func, }), }); @@ -717,9 +727,11 @@ const noop = () => {}; export const defaultDashboardEditorActionsProps = { onEditDataItem: noop, + onAddHierarchyDataItems: noop, dataSeriesFormActions: { hasAggregationsDropDown: noop, hasGrainsDropDown: noop, hasDataFilterDropdown: noop, + hasHierarchyDataItemsEnabled: noop, }, }; From e7fd749360472ba0d4c330e5a96003391a8bf018 Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Fri, 3 Oct 2025 17:17:47 +0530 Subject: [PATCH 2/9] fix: hierarchy data item handling for single select --- .../DataSeriesFormContent.jsx | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index 15ebbf5cfc..2611f254d5 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -109,6 +109,7 @@ const propTypes = { incrementNumberText: PropTypes.string, decrementNumberText: PropTypes.string, hierarchyDataItemSectionTitle: PropTypes.string, + addHierarchyDataItemLabel: PropTypes.string, }), translateWithId: PropTypes.func.isRequired, actions: DashboardEditorActionsPropTypes, @@ -149,6 +150,7 @@ const defaultProps = { incrementNumberText: 'Increment number', decrementNumberText: 'Decrement number', hierarchyDataItemSectionTitle: 'Hierarchy Data Item', + addHierarchyDataItemLabel: 'Add Hierarchy Data Item', }, getValidDataItems: null, dataItems: [], @@ -343,18 +345,16 @@ const DataSeriesFormItem = ({ const handleHierarchyDataItemChange = useCallback( (selectedItem) => { if (selectedItem && !isEmpty(selectedItem.dataItemId)) { - const selectedItems = [ - ...dataSection, - { - ...selectedItem, - // create a unique dataSourceId if it's going into attributes - // if it's going into the groupBy section then just use the dataItem ID - dataSourceId: - selectedItem?.destination === 'groupBy' - ? selectedItem.dataItemId - : `${selectedItem.dataItemId}_${uuidv4()}`, - }, - ]; + const selectedItems = canMultiSelectDataItems ? [...dataSection] : []; + selectedItems.push({ + ...selectedItem, + // create a unique dataSourceId if it's going into attributes + // if it's going into the groupBy section then just use the dataItem ID + dataSourceId: + selectedItem?.destination === 'groupBy' + ? selectedItem.dataItemId + : `${selectedItem.dataItemId}_${uuidv4()}`, + }); // need to remove the category if the card is a stacked timeseries bar const card = cardConfig.content.type === BAR_CHART_TYPES.STACKED && @@ -373,7 +373,7 @@ const DataSeriesFormItem = ({ onChange(newCard); } }, - [cardConfig, dataSection, onChange, setSelectedDataItems] + [canMultiSelectDataItems, cardConfig, dataSection, onChange, setSelectedDataItems] ); const handleEditButton = useCallback( @@ -506,32 +506,34 @@ const DataSeriesFormItem = ({ return ( <> + {(!isEmpty(validDataItems) || isHierarchyDataItemsEnabled) && ( + + )} {!isEmpty(validDataItems) && ( <> - dataSourceId)} selectedItem={ - !isEmpty(cardConfig.content?.series) + !isEmpty(dataItemListItems) && !isEmpty(cardConfig.content?.series) ? cardConfig.content?.series[0].dataItemId : null } @@ -629,7 +631,7 @@ const DataSeriesFormItem = ({ size="md" onClick={() => onAddHierarchyDataItems(handleHierarchyDataItemChange)} > - Add Hierarchy Data Item + {mergedI18n.addHierarchyDataItemLabel} Date: Mon, 6 Oct 2025 11:49:19 +0530 Subject: [PATCH 3/9] fix: update onAddHierarchyDataItems to pass cardConfig parameter --- .../DataSeriesFormItems/DataSeriesFormContent.jsx | 2 +- .../src/components/DashboardEditor/DashboardEditor.story.jsx | 3 ++- packages/react/src/components/DashboardEditor/editorUtils.jsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index 2611f254d5..57c0e06e1d 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -629,7 +629,7 @@ const DataSeriesFormItem = ({ kind="ghost" renderIcon={Add} size="md" - onClick={() => onAddHierarchyDataItems(handleHierarchyDataItemChange)} + onClick={() => onAddHierarchyDataItems(cardConfig, handleHierarchyDataItemChange)} > {mergedI18n.addHierarchyDataItemLabel} diff --git a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx index 7c3659c8fb..b56a2f9628 100644 --- a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx +++ b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx @@ -1533,7 +1533,8 @@ export const withHierarchyDataItems = () => { }); const actions = { ...commonActions, - onAddHierarchyDataItems: (cb) => cb(hierarchyDataItems), + onAddHierarchyDataItems: (cardConfig, handleHierarchyDataItemChange) => + handleHierarchyDataItemChange(hierarchyDataItems), dataSeriesFormActions: { ...commonActions.dataSeriesFormActions, hasHierarchyDataItemsEnabled: (card) => diff --git a/packages/react/src/components/DashboardEditor/editorUtils.jsx b/packages/react/src/components/DashboardEditor/editorUtils.jsx index eecdf05129..a620ee99f6 100644 --- a/packages/react/src/components/DashboardEditor/editorUtils.jsx +++ b/packages/react/src/components/DashboardEditor/editorUtils.jsx @@ -693,7 +693,7 @@ export const DashboardEditorActionsPropTypes = PropTypes.shape({ */ onEditDataItem: PropTypes.func, /** Call back function for on click of add hierarchy data items button, returns a selcted dataSource - * onAddHierarchyDataItems(handleHierarchyDataItemChange: callback function to handle hierarchy data items change) + * onAddHierarchyDataItems(cardProps: card properties, handleHierarchyDataItemChange: callback function to handle hierarchy data items change) * return: void */ onAddHierarchyDataItems: PropTypes.func, From c6bfdfad895bf57e149b0b5483c4540c9c9b76a2 Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Thu, 9 Oct 2025 14:22:34 +0530 Subject: [PATCH 4/9] fix: review comments --- .../DataSeriesFormItems/DataSeriesFormContent.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index 57c0e06e1d..6b2be7c79b 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -439,7 +439,9 @@ const DataSeriesFormItem = ({ (data, isHierarchy = false) => data ?.filter((dataItem) => - isHierarchyDataItemsEnabled ? Boolean(dataItem.resourceData?.uuid) === isHierarchy : true + !isHierarchyDataItemsEnabled + ? true + : dataItem.hasOwnProperty('resourceData') === isHierarchy ) .map((dataItem, i) => { const colorIndex = (i + removedItemsCountRef.current) % DATAITEM_COLORS_OPTIONS.length; From eb9f63ff1f518c56f5cf02a69c059c08b82561aa Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Fri, 10 Oct 2025 09:51:53 +0530 Subject: [PATCH 5/9] fix: update hierarchy data item handling to support multiple selections --- .../DataSeriesFormContent.jsx | 58 +++++++++---------- .../DashboardEditor/DashboardEditor.story.jsx | 28 ++++++--- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index 6b2be7c79b..8a97c6de24 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -343,35 +343,35 @@ const DataSeriesFormItem = ({ ); const handleHierarchyDataItemChange = useCallback( - (selectedItem) => { - if (selectedItem && !isEmpty(selectedItem.dataItemId)) { - const selectedItems = canMultiSelectDataItems ? [...dataSection] : []; - selectedItems.push({ - ...selectedItem, - // create a unique dataSourceId if it's going into attributes - // if it's going into the groupBy section then just use the dataItem ID - dataSourceId: - selectedItem?.destination === 'groupBy' - ? selectedItem.dataItemId - : `${selectedItem.dataItemId}_${uuidv4()}`, - }); - // need to remove the category if the card is a stacked timeseries bar - const card = - cardConfig.content.type === BAR_CHART_TYPES.STACKED && - cardConfig.content.timeDataSourceId && - selectedItems.length > 1 - ? omit(cardConfig, 'content.categoryDataSourceId') - : cardConfig; - const newCard = handleDataSeriesChange( - selectedItems, - card, - setEditDataSeries, - undefined, - removedItemsCountRef - ); - setSelectedDataItems(selectedItems.map(({ dataSourceId }) => dataSourceId)); - onChange(newCard); - } + (items) => { + const updatedItems = items.map((item) => ({ + ...item, + // create a unique dataSourceId if it's going into attributes + // if it's going into the groupBy section then just use the dataItem ID + dataSourceId: + item?.destination === 'groupBy' ? item.dataItemId : `${item.dataItemId}_${uuidv4()}`, + })); + + const selectedItems = canMultiSelectDataItems + ? [...dataSection, ...updatedItems] + : [updatedItems[0]]; + + // need to remove the category if the card is a stacked timeseries bar + const card = + cardConfig.content.type === BAR_CHART_TYPES.STACKED && + cardConfig.content.timeDataSourceId && + selectedItems.length > 1 + ? omit(cardConfig, 'content.categoryDataSourceId') + : cardConfig; + const newCard = handleDataSeriesChange( + selectedItems, + card, + setEditDataSeries, + undefined, + removedItemsCountRef + ); + setSelectedDataItems(selectedItems.map(({ dataSourceId }) => dataSourceId)); + onChange(newCard); }, [canMultiSelectDataItems, cardConfig, dataSection, onChange, setSelectedDataItems] ); diff --git a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx index b56a2f9628..619810aa0e 100644 --- a/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx +++ b/packages/react/src/components/DashboardEditor/DashboardEditor.story.jsx @@ -1521,16 +1521,26 @@ export const withGetDefaultCard = () => { withGetDefaultCard.storyName = 'With get default card'; export const withHierarchyDataItems = () => { - const hierarchyDataItems = object('hierarchyDataItems', { - dataItemId: 'speed', - // eslint-disable-next-line no-plusplus - label: `speed:Device`, - resourceData: { - type: 'DEVICE', - uuid: '12345', - deviceTypeUUId: '7890', + const hierarchyDataItems = object('hierarchyDataItems', [ + { + dataItemId: 'speed', + label: `speed:Device1`, + resourceData: { + type: 'DEVICE', + uuid: '12345', + deviceTypeUUId: '7890', + }, }, - }); + { + dataItemId: 'pressure', + label: `pressure:Asset1`, + resourceData: { + type: 'ASSET', + uuid: '2', + siteUUId: '1', + }, + }, + ]); const actions = { ...commonActions, onAddHierarchyDataItems: (cardConfig, handleHierarchyDataItemChange) => From 51fec5758674fbadb405d551644b16df56bac561 Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Mon, 13 Oct 2025 11:27:10 +0530 Subject: [PATCH 6/9] fix: simplify hierarchy data item filtering logic --- .../DataSeriesFormItems/DataSeriesFormContent.jsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index 8a97c6de24..ff137b3091 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -438,11 +438,7 @@ const DataSeriesFormItem = ({ const generateListItems = useCallback( (data, isHierarchy = false) => data - ?.filter((dataItem) => - !isHierarchyDataItemsEnabled - ? true - : dataItem.hasOwnProperty('resourceData') === isHierarchy - ) + ?.filter((dataItem) => dataItem.hasOwnProperty('resourceData') === isHierarchy) .map((dataItem, i) => { const colorIndex = (i + removedItemsCountRef.current) % DATAITEM_COLORS_OPTIONS.length; const iconColorOption = dataItem.color || DATAITEM_COLORS_OPTIONS[colorIndex]; @@ -486,14 +482,7 @@ const DataSeriesFormItem = ({ }, }; }) || [], - [ - cardConfig.type, - handleEditButton, - handleRemoveButton, - isHierarchyDataItemsEnabled, - mergedI18n.edit, - mergedI18n.remove, - ] + [cardConfig.type, handleEditButton, handleRemoveButton, mergedI18n.edit, mergedI18n.remove] ); const dataItemListItems = useMemo( From f188a3f2d72a4c88c590eecf82148f142648e281 Mon Sep 17 00:00:00 2001 From: Vivek Domadiya Date: Mon, 13 Oct 2025 11:35:41 +0530 Subject: [PATCH 7/9] feat: add hierarchy data item support for table cards --- .../DataSeriesFormContent.jsx | 104 ++++++---------- .../HierarchyDataFormItems.jsx | 84 +++++++++++++ .../TableCardFormContent.jsx | 111 ++++++++++++------ .../DashboardEditor/DashboardEditor.story.jsx | 5 +- 4 files changed, 202 insertions(+), 102 deletions(-) create mode 100644 packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/HierarchyDataFormItems/HierarchyDataFormItems.jsx diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx index ff137b3091..58f34e2d39 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/DataSeriesFormItems/DataSeriesFormContent.jsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useCallback, useRef } from 'react'; import PropTypes from 'prop-types'; -import { Add, Edit, MisuseOutline } from '@carbon/react/icons'; +import { Edit, MisuseOutline } from '@carbon/react/icons'; import { omit, isEmpty } from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; import hash from 'object-hash'; @@ -20,6 +20,9 @@ import { Dropdown } from '../../../../Dropdown'; import DataSeriesFormItemModal from '../DataSeriesFormItemModal'; import { CARD_TYPES, BAR_CHART_TYPES } from '../../../../../constants/LayoutConstants'; import ContentFormItemTitle from '../ContentFormItemTitle'; +import HierarchyDataFormItems, { + isHierarchyDataItem, +} from '../HierarchyDataFormItems/HierarchyDataFormItems'; import BarChartDataSeriesContent from './BarChartDataSeriesContent'; @@ -108,8 +111,6 @@ const propTypes = { closeMenuText: PropTypes.string, incrementNumberText: PropTypes.string, decrementNumberText: PropTypes.string, - hierarchyDataItemSectionTitle: PropTypes.string, - addHierarchyDataItemLabel: PropTypes.string, }), translateWithId: PropTypes.func.isRequired, actions: DashboardEditorActionsPropTypes, @@ -149,8 +150,6 @@ const defaultProps = { closeMenuText: 'Close menu', incrementNumberText: 'Increment number', decrementNumberText: 'Decrement number', - hierarchyDataItemSectionTitle: 'Hierarchy Data Item', - addHierarchyDataItemLabel: 'Add Hierarchy Data Item', }, getValidDataItems: null, dataItems: [], @@ -261,11 +260,7 @@ const DataSeriesFormItem = ({ translateWithId, actions, }) => { - const { - onEditDataItem, - onAddHierarchyDataItems, - dataSeriesFormActions: { hasHierarchyDataItemsEnabled }, - } = actions; + const { onEditDataItem } = actions; const mergedI18n = useMemo(() => ({ ...defaultProps.i18n, ...i18n }), [i18n]); const [showEditor, setShowEditor] = useState(false); @@ -282,12 +277,6 @@ const DataSeriesFormItem = ({ ? cardConfig?.content?.series : cardConfig?.content?.attributes; - // determine if hierarchy data items are available - const isHierarchyDataItemsEnabled = useMemo( - () => hasHierarchyDataItemsEnabled && hasHierarchyDataItemsEnabled(cardConfig), - [cardConfig, hasHierarchyDataItemsEnabled] - ); - const removedItemsCountRef = useRef(0); const validDataItems = useMemo( @@ -438,7 +427,7 @@ const DataSeriesFormItem = ({ const generateListItems = useCallback( (data, isHierarchy = false) => data - ?.filter((dataItem) => dataItem.hasOwnProperty('resourceData') === isHierarchy) + ?.filter((dataItem) => isHierarchyDataItem(dataItem) === isHierarchy) .map((dataItem, i) => { const colorIndex = (i + removedItemsCountRef.current) % DATAITEM_COLORS_OPTIONS.length; const iconColorOption = dataItem.color || DATAITEM_COLORS_OPTIONS[colorIndex]; @@ -497,32 +486,30 @@ const DataSeriesFormItem = ({ return ( <> - {(!isEmpty(validDataItems) || isHierarchyDataItemsEnabled) && ( - - )} + {!isEmpty(validDataItems) && ( <> )} - {isHierarchyDataItemsEnabled && ( - <> - - - - - } - title="" - items={hierarchyDataItemListItems} - /> - - )} + ); }; diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/HierarchyDataFormItems/HierarchyDataFormItems.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/HierarchyDataFormItems/HierarchyDataFormItems.jsx new file mode 100644 index 0000000000..ec26b294dc --- /dev/null +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/HierarchyDataFormItems/HierarchyDataFormItems.jsx @@ -0,0 +1,84 @@ +import React, { useMemo } from 'react'; +import PropTypes from 'prop-types'; +import { Add } from '@carbon/icons-react'; + +import { defaultDashboardEditorActionsProps } from '../../../../DashboardEditor/editorUtils'; +import ContentFormItemTitle from '../ContentFormItemTitle'; +import Button from '../../../../Button'; +import List from '../../../../List/List'; + +export const isHierarchyDataItem = (dataItem) => dataItem.hasOwnProperty('resourceData'); + +const propTypes = { + cardConfig: PropTypes.shape({}).isRequired, + /** Hierarchy Data items to display */ + hierarchyDataItemListItems: PropTypes.arrayOf(PropTypes.shape({})), + /** Handler for hierarchy data item changes */ + handleHierarchyDataItemChange: PropTypes.func.isRequired, + /** Class name for the list */ + listClassName: PropTypes.string, + i18n: PropTypes.shape({ + hierarchyDataItemSectionTitle: PropTypes.string, + addHierarchyDataItemLabel: PropTypes.string, + }), + actions: defaultDashboardEditorActionsProps, +}; + +const defaultProps = { + hierarchyDataItemListItems: [], + listClassName: '', + i18n: { + hierarchyDataItemSectionTitle: 'Hierarchy Data Item', + addHierarchyDataItemLabel: 'Add Hierarchy Data Item', + }, + actions: defaultDashboardEditorActionsProps, +}; + +const HierarchyDataFormItems = ({ + cardConfig, + hierarchyDataItemListItems, + handleHierarchyDataItemChange, + listClassName, + i18n, + actions, +}) => { + const mergedI18n = { ...defaultProps.i18n, ...i18n }; + const { + onAddHierarchyDataItems, + dataSeriesFormActions: { hasHierarchyDataItemsEnabled }, + } = actions; + + // determine if hierarchy data items are available + const isHierarchyDataItemsEnabled = useMemo( + () => hasHierarchyDataItemsEnabled && hasHierarchyDataItemsEnabled(cardConfig), + [cardConfig, hasHierarchyDataItemsEnabled] + ); + + return ( + isHierarchyDataItemsEnabled && ( + <> + + + } + title="" + items={hierarchyDataItemListItems} + /> + + ) + ); +}; + +HierarchyDataFormItems.propTypes = propTypes; +HierarchyDataFormItems.defaultProps = defaultProps; + +export default HierarchyDataFormItems; diff --git a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/TableCardFormItems/TableCardFormContent.jsx b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/TableCardFormItems/TableCardFormContent.jsx index 7b8a90d556..d194a2b0a9 100644 --- a/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/TableCardFormItems/TableCardFormContent.jsx +++ b/packages/react/src/components/CardEditor/CardEditForm/CardEditFormItems/TableCardFormItems/TableCardFormContent.jsx @@ -20,6 +20,9 @@ import DataSeriesFormItemModal from '../DataSeriesFormItemModal'; import ContentFormItemTitle from '../ContentFormItemTitle'; import { CARD_SIZES, CARD_TYPES } from '../../../../../constants/LayoutConstants'; import { formatDataItemsForDropdown } from '../DataSeriesFormItems/DataSeriesFormContent'; +import HierarchyDataFormItems, { + isHierarchyDataItem, +} from '../HierarchyDataFormItems/HierarchyDataFormItems'; const { iotPrefix } = settings; @@ -209,6 +212,24 @@ const TableCardFormContent = ({ } }; + const handleHierarchyDataItemChange = useCallback( + (items) => { + const updatedItems = items.map((item) => ({ + ...item, + // create a unique dataSourceId if it's going into attributes + // if it's going into the groupBy section then just use the dataItem ID + dataSourceId: + item?.destination === 'groupBy' ? item.dataItemId : `${item.dataItemId}_${uuidv4()}`, + })); + const selectedItems = [...dataSection, ...updatedItems]; + + const newCard = handleDataSeriesChange(selectedItems, cardConfig, null, null); + setSelectedDataItems(selectedItems.map(({ text }) => text)); + onChange(newCard); + }, + [cardConfig, dataSection, onChange, setSelectedDataItems] + ); + // need to handle thresholds from the DataSeriesFormItemModal and convert it to the right format const handleDataItemModalChanges = useCallback( (card) => { @@ -319,40 +340,52 @@ const TableCardFormContent = ({ [cardConfig, onEditDataItem, validDataItems] ); + const generateListItems = useCallback( + (data, isHierarchy = false) => + data + ?.filter((dataItem) => isHierarchyDataItem(dataItem) === isHierarchy) + ?.map((dataItem) => ({ + id: dataItem.dataSourceId, + content: { + value: dataItem.label || dataItem.dataItemId, + icon: null, + rowActions: () => [ +