From fbd0b436f53091620db3efafc4ba137000b0a461 Mon Sep 17 00:00:00 2001 From: Keith Chong Date: Tue, 14 Jul 2026 03:34:06 -0400 Subject: [PATCH] Show matchExpressions in Progressive Sync Step Groups (#10177) Signed-off-by: Keith Chong --- locales/en/plugin__gitops-plugin.json | 11 +- locales/ja/plugin__gitops-plugin.json | 11 +- locales/ko/plugin__gitops-plugin.json | 11 +- locales/zh/plugin__gitops-plugin.json | 11 +- package.json | 3 +- .../graph/ApplicationGraphView.scss | 2 + .../application/graph/graph-utils.tsx | 4 + .../appset/AppSetMatchExpressionsTab.tsx | 58 ++++++++ .../appset/graph/ApplicationSetGraphView.tsx | 136 +++++++++++++++++- .../components/appset/graph/graph-utils.tsx | 17 ++- .../appset/graph/nodes/StepGroupComponent.tsx | 117 ++++++++------- yarn.lock | 7 +- 12 files changed, 317 insertions(+), 71 deletions(-) create mode 100644 src/gitops/components/appset/AppSetMatchExpressionsTab.tsx diff --git a/locales/en/plugin__gitops-plugin.json b/locales/en/plugin__gitops-plugin.json index ffdbf1a23..a59fb087b 100644 --- a/locales/en/plugin__gitops-plugin.json +++ b/locales/en/plugin__gitops-plugin.json @@ -97,10 +97,14 @@ "App Project": "AppProject", "Argo CD project that this ApplicationSet belongs to.": "Argo CD project that this ApplicationSet belongs to.", "Git repository URL where the ApplicationSet configuration is stored.": "Git repository URL where the ApplicationSet configuration is stored.", + "Progressive Sync Step {{x}}": "Progressive Sync Step {{x}}", "Applications": "Applications", + "Show all match expressions": "Show all match expressions", "Edit ApplicationSet": "Edit ApplicationSet", "Delete ApplicationSet": "Delete ApplicationSet", "View Graph": "View Graph", + "Match Expressions": "Match Expressions", + "Name must be unique within a namespace.": "Name must be unique within a namespace.", "AppSet ownerReference Tree View": "AppSet ownerReference Tree View", "Progressive Sync Flow View": "Progressive Sync Flow View", "Expand or collapse all progressive sync step groups": "Expand or collapse all progressive sync step groups", @@ -108,11 +112,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -284,7 +288,6 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", @@ -322,7 +325,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", - "Name must be unique within a namespace.": "Name must be unique within a namespace.", + "No labels": "No labels", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", "Edit": "Edit", diff --git a/locales/ja/plugin__gitops-plugin.json b/locales/ja/plugin__gitops-plugin.json index 2430d79c3..71094d076 100644 --- a/locales/ja/plugin__gitops-plugin.json +++ b/locales/ja/plugin__gitops-plugin.json @@ -97,10 +97,14 @@ "App Project": "AppProject", "Argo CD project that this ApplicationSet belongs to.": "Argo CD project that this ApplicationSet belongs to.", "Git repository URL where the ApplicationSet configuration is stored.": "Git repository URL where the ApplicationSet configuration is stored.", + "Progressive Sync Step {{x}}": "Progressive Sync Step {{x}}", "Applications": "Applications", + "Show all match expressions": "Show all match expressions", "Edit ApplicationSet": "Edit ApplicationSet", "Delete ApplicationSet": "Delete ApplicationSet", "View Graph": "View Graph", + "Match Expressions": "Match Expressions", + "Name must be unique within a namespace.": "Name must be unique within a namespace.", "AppSet ownerReference Tree View": "AppSet ownerReference Tree View", "Progressive Sync Flow View": "Progressive Sync Flow View", "Expand or collapse all progressive sync step groups": "Expand or collapse all progressive sync step groups", @@ -108,11 +112,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -284,7 +288,6 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", @@ -322,7 +325,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", - "Name must be unique within a namespace.": "Name must be unique within a namespace.", + "No labels": "No labels", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", "Edit": "Edit", diff --git a/locales/ko/plugin__gitops-plugin.json b/locales/ko/plugin__gitops-plugin.json index 9b2a17804..543210fee 100644 --- a/locales/ko/plugin__gitops-plugin.json +++ b/locales/ko/plugin__gitops-plugin.json @@ -97,10 +97,14 @@ "App Project": "App Project", "Argo CD project that this ApplicationSet belongs to.": "Argo CD project that this ApplicationSet belongs to.", "Git repository URL where the ApplicationSet configuration is stored.": "Git repository URL where the ApplicationSet configuration is stored.", + "Progressive Sync Step {{x}}": "Progressive Sync Step {{x}}", "Applications": "Applications", + "Show all match expressions": "Show all match expressions", "Edit ApplicationSet": "Edit ApplicationSet", "Delete ApplicationSet": "Delete ApplicationSet", "View Graph": "View Graph", + "Match Expressions": "Match Expressions", + "Name must be unique within a namespace.": "Name must be unique within a namespace.", "AppSet ownerReference Tree View": "AppSet ownerReference Tree View", "Progressive Sync Flow View": "Progressive Sync Flow View", "Expand or collapse all progressive sync step groups": "Expand or collapse all progressive sync step groups", @@ -108,11 +112,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -284,7 +288,6 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", @@ -322,7 +325,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", - "Name must be unique within a namespace.": "Name must be unique within a namespace.", + "No labels": "No labels", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", "Edit": "Edit", diff --git a/locales/zh/plugin__gitops-plugin.json b/locales/zh/plugin__gitops-plugin.json index 873866551..96fddff84 100644 --- a/locales/zh/plugin__gitops-plugin.json +++ b/locales/zh/plugin__gitops-plugin.json @@ -97,10 +97,14 @@ "App Project": "App Project", "Argo CD project that this ApplicationSet belongs to.": "Argo CD project that this ApplicationSet belongs to.", "Git repository URL where the ApplicationSet configuration is stored.": "Git repository URL where the ApplicationSet configuration is stored.", + "Progressive Sync Step {{x}}": "Progressive Sync Step {{x}}", "Applications": "Applications", + "Show all match expressions": "Show all match expressions", "Edit ApplicationSet": "Edit ApplicationSet", "Delete ApplicationSet": "Delete ApplicationSet", "View Graph": "View Graph", + "Match Expressions": "Match Expressions", + "Name must be unique within a namespace.": "Name must be unique within a namespace.", "AppSet ownerReference Tree View": "AppSet ownerReference Tree View", "Progressive Sync Flow View": "Progressive Sync Flow View", "Expand or collapse all progressive sync step groups": "Expand or collapse all progressive sync step groups", @@ -108,11 +112,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -284,7 +288,6 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", @@ -322,7 +325,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", - "Name must be unique within a namespace.": "Name must be unique within a namespace.", + "No labels": "No labels", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", "Edit": "Edit", diff --git a/package.json b/package.json index 8b2a08e8d..1ab7267b4 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,8 @@ "react-redux": "7.2.9", "react-router-dom-v5-compat": "^6.30.0", "react-router-hash-link": "2.4.3", - "redux": "4.0.1" + "redux": "4.0.1", + "yaml": "^2.9.0" }, "//TODO": [ "For @patternfly/react-icons: find another solution for icons from older react-icons version and remove this dependency." diff --git a/src/gitops/components/application/graph/ApplicationGraphView.scss b/src/gitops/components/application/graph/ApplicationGraphView.scss index 34d5cdc64..27b4baf75 100644 --- a/src/gitops/components/application/graph/ApplicationGraphView.scss +++ b/src/gitops/components/application/graph/ApplicationGraphView.scss @@ -169,6 +169,8 @@ .pf-topology__node__label__background { fill: var(--pf-t--global--background--color--floating--default); + stroke: var(--pf-t--global--border--color--default); + stroke-width: 1; } .pf-topology__node__action-icon:hover svg{ diff --git a/src/gitops/components/application/graph/graph-utils.tsx b/src/gitops/components/application/graph/graph-utils.tsx index 36b60d30a..c822918a7 100644 --- a/src/gitops/components/application/graph/graph-utils.tsx +++ b/src/gitops/components/application/graph/graph-utils.tsx @@ -48,6 +48,10 @@ const createApplicationNode = ( height: APP_NODE_HEIGHT, data: { name: application?.metadata?.name, + namespace: application?.metadata?.namespace, + kind: application?.kind, + version: ApplicationModel.apiVersion || 'v1alpha1', + group: ApplicationModel.apiGroup || 'argoproj.io', badge: 'A', badgeColor: RESOURCE_COLORS.get( RESOURCE_BADGE_COLORS.get('.co-m-resource-' + application?.kind.toLowerCase()), diff --git a/src/gitops/components/appset/AppSetMatchExpressionsTab.tsx b/src/gitops/components/appset/AppSetMatchExpressionsTab.tsx new file mode 100644 index 000000000..d0cfd770d --- /dev/null +++ b/src/gitops/components/appset/AppSetMatchExpressionsTab.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { RouteComponentProps } from 'react-router'; +import * as YamlFormatter from 'yaml'; + +import { SidebarSectionHeading } from '@gitops/topology/sidebar/DeploymentSideBarDetails'; +import { Card, CardBody, Flex, FlexItem, PageSection } from '@patternfly/react-core'; + +import { ApplicationSetKind } from '../../models/ApplicationSetModel'; +import { useGitOpsTranslation } from '../../utils/hooks/useGitOpsTranslation'; + +import './AppSetDetailsTab.scss'; + +type AppSetMatchExpressionsTabProps = RouteComponentProps<{ ns: string; name: string }> & { + obj?: ApplicationSetKind; + customData?: object; +}; + +const AppSetMatchExpressionsTab: React.FC = ({ + obj, + customData, +}) => { + const { t } = useGitOpsTranslation(); + const index = customData.step !== undefined ? parseInt(customData?.step) - 1 : -1; + if (index >= 0) { + const yaml = obj?.spec?.strategy?.rollingSync?.steps[index] || {}; + return ( + <> + + + + +
+
+ +
+
+ + +
{YamlFormatter.stringify(yaml, null, 2)}
+
+
+
+
+
+
+ + ); + } else { + return
; + } +}; + +export default AppSetMatchExpressionsTab; diff --git a/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx b/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx index f1cb78229..06a71716f 100644 --- a/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx +++ b/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx @@ -8,7 +8,9 @@ import { HealthStatus } from '@gitops/utils/constants'; import { ArgoServer, getArgoServer } from '@gitops/utils/gitops'; import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; import { + HorizontalNav, K8sModel, + ResourceLink, useAnnotationsModal, useDeleteModal, useK8sModel, @@ -16,6 +18,18 @@ import { useLabelsModal, useUserSettings, } from '@openshift-console/dynamic-plugin-sdk'; +import { + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTermHelpText, + DescriptionListTermHelpTextButton, + Flex, + FlexItem, + PageSection, + Popover, + Title, +} from '@patternfly/react-core'; import { EllipsisHIcon, ObjectGroupIcon, @@ -53,6 +67,7 @@ import { SpacerNode, TaskEdge, TopologyControlBar, + TopologySideBar, TopologyView, Visualization, VisualizationProvider, @@ -64,6 +79,7 @@ import { import { GraphResourceMenuItem } from '../../application/graph/hooks/GraphResourceMenuItems'; import { ResourceNode } from '../../application/graph/nodes/ResourceNode'; +import AppSetMatchExpressionsTab from '../AppSetMatchExpressionsTab'; import { ApplicationSetNode } from './nodes/ApplicationSetNode'; import { StepGroupComponent } from './nodes/StepGroupComponent'; @@ -143,6 +159,7 @@ interface AppSetContextMenuItemProps { launchAnnotationsModal: () => void; launchDeleteModal: () => void; navigate: NavigateFunction; + setShowMatchExpressions: (show: boolean) => void; } const getArgoHref = (argoServer: ArgoServer, namespace: string, name: string): string => { @@ -161,6 +178,7 @@ const AppSetContextMenuItem: React.FC = ({ launchAnnotationsModal, launchDeleteModal, navigate, + setShowMatchExpressions, }) => { if (label === '-') { return ; @@ -195,6 +213,9 @@ const AppSetContextMenuItem: React.FC = ({ } break; } + case t('Show all match expressions'): + setShowMatchExpressions(true); + break; case t('Edit labels'): launchLabelsModal(); break; @@ -231,6 +252,7 @@ interface ContextMenuFactoryParams { launchAnnotationsModal: () => void; launchDeleteModal: () => void; navigate: NavigateFunction; + setShowMatchExpressions: (show: boolean) => void; } const createContextMenuItems = ( @@ -282,7 +304,9 @@ const createAppSetComponentFactory = case 'group': return TransparentGroup; case 'step-group': - return withSelection()(StepGroupComponent); + return withContextMenu((graphElement) => + createContextMenuItems(graphElement, paramsRef, t('Show all match expressions')), + )(withSelection()(StepGroupComponent)); case 'filler-node': return DefaultNode; case 'task-edge': @@ -324,6 +348,105 @@ export const TreeViewLayout = { PROGRESSIVE_SYNC_FLOW_LAYOUT: 'progressive-sync-flow-layout', }; +type TopologySideBarParams = { + show: boolean; + onClose: () => void; + selectedIds: string[]; + controller: Visualization; + applicationSet: ApplicationSetKind; +}; + +const PAGES = [ + { + href: '', + name: t('Match Expressions'), + component: AppSetMatchExpressionsTab, + }, +]; + +const getTopologySideBar = ({ + show, + onClose, + selectedIds, + controller, + applicationSet, +}: TopologySideBarParams): React.ReactElement => { + const selectedNode = + selectedIds.length > 0 + ? (controller.getNodeById(selectedIds[0]) as TopologyNode | undefined) + : undefined; + + const isStepGroup = selectedNode?.getId().endsWith('-step-group'); + + const renderSidebarContent = () => { + if (selectedNode === undefined) return
; + if (isStepGroup) { + return ( +
+ +
+ ); + } + return ( +
+ + + {selectedNode?.getLabel() ?? selectedIds[0]} + + + + + + + + {t('Name')}
} + bodyContent={
{t('Name must be unique within a namespace.')}
} + > + + {t('Name')} + + + + + + + + + + + + + + + + ); + }; + + return ( + +
{renderSidebarContent()}
+
+ ); +}; + export const ApplicationSetGraphView: React.FC<{ applicationSet: ApplicationSetKind; applications: ApplicationKind[]; @@ -352,6 +475,7 @@ export const ApplicationSetGraphView: React.FC<{ // Track expanded step-groups - only expanded step-groups have their app nodes included in initialNodes const [expandedStepGroups, setExpandedStepGroups] = React.useState>(new Set()); const [selectedIds, setSelectedIds] = React.useState([]); + const [showMatchExpressions, setShowMatchExpressions] = React.useState(false); const [renderKey, setRenderKey] = React.useState(0); // Track if initial collapse is pending - hide graph until complete to avoid flicker const skipExpandGroupsEffectRef = React.useRef(false); @@ -375,6 +499,7 @@ export const ApplicationSetGraphView: React.FC<{ launchAnnotationsModal, launchDeleteModal, navigate, + setShowMatchExpressions, }); React.useEffect(() => { @@ -386,6 +511,7 @@ export const ApplicationSetGraphView: React.FC<{ launchAnnotationsModal, launchDeleteModal, navigate, + setShowMatchExpressions, }; }, [ model, @@ -395,6 +521,7 @@ export const ApplicationSetGraphView: React.FC<{ launchAnnotationsModal, launchDeleteModal, navigate, + setShowMatchExpressions, ]); const initialNodes = getInitialNodes( @@ -798,6 +925,13 @@ export const ApplicationSetGraphView: React.FC<{ setShowMatchExpressions(false), + selectedIds, + controller, + applicationSet, + })} controlBar={ { const stepGroupApps = stepGroupAppsMap.get(step); + const stepMatchExpression = + applicationSet.spec?.strategy?.rollingSync?.steps?.[ + parseInt(step) - 1 + ]?.matchExpressions?.at(0); + const key = stepMatchExpression?.key || ''; + const operator = stepMatchExpression?.operator || ''; + const values = stepMatchExpression?.values?.[0] || ''; if (stepGroupApps.length === 0 && hasApplicationStatus) { stepGroupAppsMap.set(step.toString(), [step + '-filler-node']); initialNodes.push({ @@ -269,7 +282,7 @@ export const getInitialNodes = ( type: 'step-group', group: true, rank: 3, - label: 'Progressive Sync Step ' + step, + label: key + ' ' + operator + ' ' + values, children: [...stepGroupApps], data: { step: step, diff --git a/src/gitops/components/appset/graph/nodes/StepGroupComponent.tsx b/src/gitops/components/appset/graph/nodes/StepGroupComponent.tsx index b2f50730b..40d69c1b7 100644 --- a/src/gitops/components/appset/graph/nodes/StepGroupComponent.tsx +++ b/src/gitops/components/appset/graph/nodes/StepGroupComponent.tsx @@ -7,6 +7,7 @@ import { } from '@gitops/components/application/graph/icons/resource-colours'; import { APP_NODE_HEIGHT } from '@gitops/components/graph/utils'; import { successColor, warningColor } from '@gitops/utils/components/Icons/Icons'; +import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; import ApplicationIcon from '@images/resources/application.svg'; import { Tooltip } from '@patternfly/react-core'; import { HeartIcon, PauseIcon, SyncAltIcon, WarningTriangleIcon } from '@patternfly/react-icons'; @@ -22,6 +23,7 @@ import { RectAnchor, ShapeProps, useAnchor, + WithContextMenuProps, WithSelectionProps, } from '@patternfly/react-topology'; import styles from '@patternfly/react-topology/dist/esm/css/topology-components'; @@ -265,58 +267,73 @@ const DummyShape: React.FunctionComponent = ({ }; // This is the collapsible group of applications that have the same step, determined by the matched expression defined in the ApplicationSet -export const StepGroupComponent: React.FC = - observer(({ element, onSelect, selected }) => { - useAnchor(RightAnchor, AnchorEnd.source, 'task-edge'); - useAnchor(LeftAnchor, AnchorEnd.target, 'task-edge'); +export const StepGroupComponent: React.FC< + WithContextMenuProps & WithSelectionProps & { element: TopologyNode } +> = observer(({ element, onContextMenu, contextMenuOpen, onSelect, selected }) => { + useAnchor(RightAnchor, AnchorEnd.source, 'task-edge'); + useAnchor(LeftAnchor, AnchorEnd.target, 'task-edge'); - const handleCollapseChange = React.useCallback((group: TopologyNode, collapsed: boolean) => { - const stepGroup = group - .getGraph() - .getChildren() - .find((c) => c.getId() === 'transparent-group-of-step-groups-' + group.getData()?.step); - if (stepGroup) { - stepGroup.getData().isCollapsed = collapsed; - } - // Update expanded step-groups state to trigger structural refresh - // Get callback from controller (not graph) since graph may be replaced during model updates - const graph = group.getGraph(); - const controller = graph?.getController?.(); - const updateExpanded = (controller as any)?.updateExpandedStepGroups; - if (updateExpanded) { - updateExpanded(group.getId(), !collapsed); + const handleCollapseChange = React.useCallback((group: TopologyNode, collapsed: boolean) => { + const stepGroup = group + .getGraph() + .getChildren() + .find((c) => c.getId() === 'transparent-group-of-step-groups-' + group.getData()?.step); + if (stepGroup) { + stepGroup.getData().isCollapsed = collapsed; + } + // Update expanded step-groups state to trigger structural refresh + // Get callback from controller (not graph) since graph may be replaced during model updates + const graph = group.getGraph(); + const controller = graph?.getController?.(); + const updateExpanded = (controller as any)?.updateExpandedStepGroups; + if (updateExpanded) { + updateExpanded(group.getId(), !collapsed); + } + }, []); + + const handleContextMenu = React.useCallback( + (e: React.MouseEvent) => { + if (!selected) { + onSelect?.(e); } - }, []); + onContextMenu?.(e); + }, + [onSelect, onContextMenu, selected], + ); - return ( - DummyShape} - labelClassName={css('gitops-step-group-label', selected && 'pf-m-selected')} - onCollapseChange={handleCollapseChange} - onSelect={onSelect} - selected={selected} - > - - - ); - }); + return ( + DummyShape} + labelClassName={css('gitops-step-group-label', selected && 'pf-m-selected')} + onCollapseChange={handleCollapseChange} + onContextMenu={handleContextMenu} + contextMenuOpen={contextMenuOpen} + onSelect={onSelect} + selected={selected} + > + + + ); +}); export default StepGroupComponent; diff --git a/yarn.lock b/yarn.lock index 33ed44c1f..b75e55a78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7387,7 +7387,7 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.21, lodash-es@^4.17.23, lodash-es@^4.18.1: +lodash-es@^4.17.21, lodash-es@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d" integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== @@ -10555,6 +10555,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.9.0.tgz#78274afd93598a1dfdd6130df6a566defcbf9aa4" + integrity sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"