From 12055532d7fe591219b39ed5966e1aef883d304d Mon Sep 17 00:00:00 2001 From: rowheat02 Date: Thu, 7 May 2026 15:46:39 +0545 Subject: [PATCH 1/5] Apply Dimension base implementation with bugs fixes --- .../widgets/builder/wizard/FilterWizard.jsx | 2 +- .../interactions/InteractionButtons.jsx | 28 +- .../InteractionEventsSelector.jsx | 53 +-- .../interactions/InteractionsEditor.jsx | 27 +- .../common/interactions/InteractionsRow.jsx | 53 ++- .../__tests__/InteractionsRow-test.jsx | 93 +++++ .../interactions/interaction-wizard.less | 14 +- .../common/interactions/interactionHelpers.js | 43 -- .../wizard/filter/FilterActionsTab.jsx | 178 +++++++- .../hooks/__tests__/useAttributeSync-test.jsx | 95 +++++ .../FilterDataTab/hooks/useAttributeSync.js | 32 +- .../wizard/filter/FilterDataTab/index.jsx | 8 +- .../widgets/enhancers/filterWidget.js | 24 +- .../widgets/widget/FilterWidget.jsx | 16 +- .../epics/__tests__/interactions-test.js | 128 +++++- web/client/epics/interactions.js | 120 +++++- .../plugins/widgetbuilder/FilterView.jsx | 97 ++++- web/client/selectors/widgets.js | 4 +- web/client/translations/data.de-DE.json | 5 +- web/client/translations/data.en-US.json | 5 +- web/client/translations/data.es-ES.json | 5 +- web/client/translations/data.fr-FR.json | 5 +- web/client/translations/data.it-IT.json | 5 +- web/client/utils/InteractionUtils.js | 379 +++++++++++++----- .../utils/__tests__/InteractionUtils-test.js | 211 +++++++++- 25 files changed, 1379 insertions(+), 251 deletions(-) create mode 100644 web/client/components/widgets/builder/wizard/common/interactions/__tests__/InteractionsRow-test.jsx create mode 100644 web/client/components/widgets/builder/wizard/filter/FilterDataTab/hooks/__tests__/useAttributeSync-test.jsx diff --git a/web/client/components/widgets/builder/wizard/FilterWizard.jsx b/web/client/components/widgets/builder/wizard/FilterWizard.jsx index 2a14013a0f9..81962fd3fe1 100644 --- a/web/client/components/widgets/builder/wizard/FilterWizard.jsx +++ b/web/client/components/widgets/builder/wizard/FilterWizard.jsx @@ -95,7 +95,7 @@ const FilterWizard = ({ }, [editorData, setValid]); const tabContents = { - data: , + data: , layout: , actions: }; diff --git a/web/client/components/widgets/builder/wizard/common/interactions/InteractionButtons.jsx b/web/client/components/widgets/builder/wizard/common/interactions/InteractionButtons.jsx index b5a45ff285c..eff3789b82c 100644 --- a/web/client/components/widgets/builder/wizard/common/interactions/InteractionButtons.jsx +++ b/web/client/components/widgets/builder/wizard/common/interactions/InteractionButtons.jsx @@ -24,38 +24,27 @@ const TButton = tooltip(Button); * @param {function} setShowConfiguration toggles the UI for configuration * @param {boolean} isPluggable tells if the interaction can be plugged or not * @param {boolean} isConfigurable tells if the interaction can be configured or not + * @param {object} plugConstraints describes whether plugging is disabled and why * @returns {React.ReactElement} */ -const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowConfiguration = () => {}, isPluggable, isConfigurable, notConnectableForSpecialCase, notConnectableForSpecialCaseMsg}) => { - - if (notConnectableForSpecialCase) { - return ( - - } - className="square-button" - > - - - - ); - } +const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowConfiguration = () => {}, isPluggable, isConfigurable, plugConstraints = {} }) => { + const { disabled: isPlugConstrained = false, reason: plugConstraintReason = null } = plugConstraints; return ( - + {isConfigurable && setShowConfiguration(!showConfiguration)} borderTransparent - tooltip={} + tooltip={plugConstraintReason || } variant={showConfiguration ? "primary" : undefined} > } setPlugged(!plugged)} borderTransparent variant={plugged ? "success" : undefined} @@ -67,4 +56,3 @@ const InteractionButtons = ({ plugged, setPlugged, showConfiguration, setShowCon }; export default InteractionButtons; - diff --git a/web/client/components/widgets/builder/wizard/common/interactions/InteractionEventsSelector.jsx b/web/client/components/widgets/builder/wizard/common/interactions/InteractionEventsSelector.jsx index 437b4f975fb..b813c971100 100644 --- a/web/client/components/widgets/builder/wizard/common/interactions/InteractionEventsSelector.jsx +++ b/web/client/components/widgets/builder/wizard/common/interactions/InteractionEventsSelector.jsx @@ -14,25 +14,24 @@ import { Glyphicon } from 'react-bootstrap'; import { getWidgetInteractionTreeGenerated, getEditingWidget, getAllInteractionsWhileEditingSelector } from '../../../../../../selectors/widgets'; import InteractionTargetsList from './InteractionTargetsList'; import './interaction-wizard.less'; -import { detachSingleChildCollections, filterTreeWithTarget } from '../../../../../../utils/InteractionUtils'; +import { detachSingleChildCollections, filterTreeWithTarget, filterDimensionTreeByValueAttributeType } from '../../../../../../utils/InteractionUtils'; import Message from '../../../../../I18N/Message'; -const targetTitleTranslationMap = { - "Apply filter": "widgets.filterWidget.applyFilter", - "Apply style": "widgets.filterWidget.applyStyle" -}; - - -const InteractionEventsSelector = ({target, expanded, toggleExpanded = () => {}, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, alreadyExistingInteractions}) => { +export const InteractionEventsSelector = ({target, expanded, toggleExpanded = () => {}, interactionTree, interactions, sourceWidgetId, currentSourceId, onEditorChange, alreadyExistingInteractions, valueAttributeType, targetTitleMsgIds = {}}) => { const filteredInteractionTree = useMemo(() => { - const filteredTree = filterTreeWithTarget(interactionTree, target) || []; - return detachSingleChildCollections(filteredTree, ['widgets', 'traces', "layers"]); - }, [interactionTree]); + const filteredTree = filterTreeWithTarget(interactionTree, target) || { children: [] }; + const dimensionFilteredTree = target?.targetType === 'applyDimension' + ? filterDimensionTreeByValueAttributeType(filteredTree, valueAttributeType) + : filteredTree; + return detachSingleChildCollections(dimensionFilteredTree, ['widgets', 'traces', "map", "layers"]) || { children: [] }; + }, [interactionTree, target, valueAttributeType]); + const hasConnectableNodes = (filteredInteractionTree?.children || []).length > 0; + const targetTitleMsgId = targetTitleMsgIds[target.targetType]; return ( - + - + + {targetTitleMsgId ? : target.title} + {expanded && - + {hasConnectableNodes ? ( + + ) : ( + + + + )} } ); diff --git a/web/client/components/widgets/builder/wizard/common/interactions/InteractionsEditor.jsx b/web/client/components/widgets/builder/wizard/common/interactions/InteractionsEditor.jsx index 2f104d6954d..e3556a52750 100644 --- a/web/client/components/widgets/builder/wizard/common/interactions/InteractionsEditor.jsx +++ b/web/client/components/widgets/builder/wizard/common/interactions/InteractionsEditor.jsx @@ -1,9 +1,18 @@ import React, { useEffect, useState } from 'react'; import InteractionEventsSelector from "./InteractionEventsSelector"; import { TARGET_TYPES } from '../../../../../../utils/InteractionUtils'; +import FlexBox from '../../../../../layout/FlexBox'; // currentSourceId is the source that will be source of the target, in filter widget 'filterId' is expected -const InteractionEditor = ({targets = [], sourceWidgetId, currentSourceId, onEditorChange = () => {}, isStyleOnly = false}) => { +const InteractionEditor = ({ + targets = [], + sourceWidgetId, + currentSourceId, + onEditorChange = () => {}, + isStyleOnly = false, + valueAttributeType, + targetTitleMsgIds = {} +}) => { const initialExpandedItems = targets.length > 0 ? [targets[0].targetType] : []; const [expandedItems, setExpandedItems] = useState(initialExpandedItems); const toggleExpanded = (name) => { @@ -21,7 +30,17 @@ const InteractionEditor = ({targets = [], sourceWidgetId, currentSourceId, onEdi } }, [isStyleOnly]); - return <> + useEffect(() => { + if (targets.some(target => target.targetType === TARGET_TYPES.APPLY_DIMENSION)) { + setExpandedItems(items => + items.includes(TARGET_TYPES.APPLY_DIMENSION) + ? items + : [...items, TARGET_TYPES.APPLY_DIMENSION] + ); + } + }, [targets]); + + return ( {targets.map(e => { const expanded = expandedItems.includes(e.targetType); return (); })} - ; + ); }; export default InteractionEditor; diff --git a/web/client/components/widgets/builder/wizard/common/interactions/InteractionsRow.jsx b/web/client/components/widgets/builder/wizard/common/interactions/InteractionsRow.jsx index 0225f08eaed..67e59440914 100644 --- a/web/client/components/widgets/builder/wizard/common/interactions/InteractionsRow.jsx +++ b/web/client/components/widgets/builder/wizard/common/interactions/InteractionsRow.jsx @@ -20,6 +20,7 @@ import InteractionConfiguration from './InteractionConfiguration'; import { buildInteractionObject, matchesInteraction } from './interactionHelpers'; import { DEFAULT_CONFIGURATION } from './interactionConstants'; import Message from '../../../../../I18N/Message'; +import tooltip from '../../../../../misc/enhancers/tooltip'; import LocalizedString from '../../../../../I18N/LocalizedString'; @@ -28,6 +29,9 @@ const itemTitleTranslationMap = { "Map": "widgets.filterWidget.map" }; +const isMapTimeTargetNodePath = (nodePath) => nodePath === 'map.time' || /(?:^|\.)maps\[[^\]]+\]\.time$/.test(nodePath); +const TFlexBox = tooltip(FlexBox); + const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactionTree, currentSourceId, onEditorChange, alreadyExistingInteractions}) => { // from interactions we can derive if the target is plugged or not, and its configuration @@ -57,6 +61,14 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio // Get configuration directly from existing interaction or use default const configuration = existingInteraction?.configuration || DEFAULT_CONFIGURATION; const plugged = existingInteraction?.plugged || false; + const sourcePluggedConnections = alreadyExistingInteractions.filter(i => + i?.plugged === true + && i?.source?.nodePath === sourceNodePath + ); + const hasMapTimeConnection = sourcePluggedConnections.some(i => isMapTimeTargetNodePath(i?.target?.nodePath)); + const hasNonMapTimeConnection = sourcePluggedConnections.some(i => !isMapTimeTargetNodePath(i?.target?.nodePath)); + const currentItemIsMapTime = isMapTimeTargetNodePath(targetNodePath); + const mapTimeLockConflict = (hasMapTimeConnection && !currentItemIsMapTime) || (hasNonMapTimeConnection && currentItemIsMapTime); const { directlyPluggable, configuredToForcePlug } = getItemPluggableStatus(item, target, configuration); @@ -110,9 +122,42 @@ const InteractionsRow = ({item, target, interactions, sourceWidgetId, interactio return alreadyExistingInteractions.filter(i => i.source.nodePath !== sourceNodePath).some(i => (i.targetType === TARGET_TYPES.APPLY_STYLE && target.targetType === TARGET_TYPES.APPLY_STYLE) && i.target.nodePath === targetNodePath && i.plugged); }, [alreadyExistingInteractions, targetNodePath, sourceNodePath]); + const plugConstraints = useMemo(() => { + if (styleAlreadyConnected) { + return { + disabled: true, + reason: + }; + } + + if (mapTimeLockConflict) { + return { + disabled: true, + reason: + }; + } + + return { + disabled: false, + reason: null + }; + }, [styleAlreadyConnected, mapTimeLockConflict]); + const rowPlugConstraints = item.type === 'element' + ? plugConstraints + : { + disabled: false, + reason: null + }; + return ( - + {hasChildren && (