Skip to content

Commit 5a5aee0

Browse files
authored
Merge pull request #246 from aali309/gitops-9972
fix: AppProject navigation broken from from Application details page
2 parents 01e9db1 + 4a53183 commit 5a5aee0

14 files changed

Lines changed: 480 additions & 235 deletions

‎src/gitops/components/application/ApplicationDetailsTab.tsx‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import HealthStatus from '@gitops/Statuses/HealthStatus';
1212
import { OperationState } from '@gitops/Statuses/OperationState';
1313
import SyncStatus from '@gitops/Statuses/SyncStatus';
1414
import { ArgoServer, getArgoServer, getFriendlyClusterName } from '@gitops/utils/gitops';
15+
import { labelControllerNamespaceKey } from '@gitops/utils/gitops';
1516
import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation';
1617
import { useObjectModifyPermissions } from '@gitops/utils/utils';
1718
import { k8sUpdate, ResourceLink, useK8sModel } from '@openshift-console/dynamic-plugin-sdk';
@@ -114,6 +115,15 @@ const ApplicationDetailsTab: React.FC<ApplicationDetailsTabProps> = ({ obj }) =>
114115
sources = [];
115116
revisions = [];
116117
}
118+
119+
// Get the controller namespace for AppProject link
120+
const getControllerNamespace = (): string => {
121+
if (obj?.status?.controllerNamespace) return obj.status.controllerNamespace;
122+
if (obj?.metadata?.labels?.[labelControllerNamespaceKey])
123+
return obj.metadata.labels[labelControllerNamespaceKey];
124+
return obj?.metadata?.namespace || '';
125+
};
126+
117127
return (
118128
<div>
119129
<PageSection
@@ -214,9 +224,8 @@ const ApplicationDetailsTab: React.FC<ApplicationDetailsTabProps> = ({ obj }) =>
214224
title={t('Project')}
215225
help={t('The Argo CD Project that this application belongs to.')}
216226
>
217-
{/* TODO - Update to handle App in Any Namespace when controller namespace is in status */}
218227
<ResourceLink
219-
namespace={obj?.metadata?.namespace}
228+
namespace={getControllerNamespace()}
220229
groupVersionKind={{
221230
group: 'argoproj.io',
222231
version: 'v1alpha1',

‎src/gitops/components/application/graph/ApplicationGraphView.tsx‎

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,19 @@ const customLayoutFactory: LayoutFactory = (type: string, graph: Graph): Layout
8080
});
8181
};
8282

83+
interface ApplicationContextMenuParams {
84+
application: ApplicationKind;
85+
navigate: NavigateFunction;
86+
launchLabelsModal: () => void;
87+
launchAnnotationsModal: () => void;
88+
launchDeleteModal: () => void;
89+
setGroupNodeStates: React.Dispatch<React.SetStateAction<string[]>>;
90+
}
91+
8392
const customComponentFactory =
8493
(
8594
hrefRef: React.MutableRefObject<string>,
86-
application: ApplicationKind,
87-
navigate: NavigateFunction,
88-
launchLabelsModal: () => void,
89-
launchAnnotationsModal: () => void,
90-
launchDeleteModal: () => void,
91-
setGroupNodeStates: React.Dispatch<React.SetStateAction<string[]>>,
95+
paramsRef: React.RefObject<ApplicationContextMenuParams>,
9296
): ComponentFactory =>
9397
(kind: ModelKind, type: string) => {
9498
const createContextMenuItems = (
@@ -132,11 +136,15 @@ const customComponentFactory =
132136
<ContextMenuItem
133137
key={label}
134138
onClick={() => {
139+
const params = paramsRef.current;
140+
if (!params) {
141+
return;
142+
}
135143
if (label === t('View in Argo CD')) {
136144
window.open(hrefRef.current, '_blank');
137145
}
138146
if (label === t('View Details')) {
139-
navigate(graphElement.getData().resourcePath);
147+
params.navigate(graphElement.getData().resourcePath);
140148
}
141149
}}
142150
>
@@ -180,6 +188,18 @@ const customComponentFactory =
180188
<ContextMenuItem
181189
key={label}
182190
onClick={async () => {
191+
const params = paramsRef.current;
192+
if (!params) {
193+
return;
194+
}
195+
const {
196+
application,
197+
navigate,
198+
launchLabelsModal,
199+
launchAnnotationsModal,
200+
launchDeleteModal,
201+
setGroupNodeStates,
202+
} = params;
183203
if (label === t('View in Argo CD')) {
184204
window.open(hrefRef.current, '_blank');
185205
} else if (label === t('Edit labels')) {
@@ -339,22 +359,34 @@ export const ApplicationGraphView: React.FC<{
339359
const launchDeleteModal = useDeleteModal(application);
340360
const navigate = useNavigate();
341361

362+
const contextMenuParamsRef = React.useRef<ApplicationContextMenuParams>({
363+
application,
364+
navigate,
365+
launchLabelsModal,
366+
launchAnnotationsModal,
367+
launchDeleteModal,
368+
setGroupNodeStates,
369+
});
370+
371+
React.useEffect(() => {
372+
contextMenuParamsRef.current = {
373+
application,
374+
navigate,
375+
launchLabelsModal,
376+
launchAnnotationsModal,
377+
launchDeleteModal,
378+
setGroupNodeStates,
379+
};
380+
}, [application, navigate, launchLabelsModal, launchAnnotationsModal, launchDeleteModal]);
381+
342382
const controller = React.useMemo(() => {
343383
const newController = new Visualization();
344384
newController.registerLayoutFactory(customLayoutFactory);
345-
newController.registerComponentFactory(
346-
customComponentFactory(
347-
hrefRef,
348-
application,
349-
navigate,
350-
launchLabelsModal,
351-
launchAnnotationsModal,
352-
launchDeleteModal,
353-
setGroupNodeStates,
354-
),
355-
);
385+
newController.registerComponentFactory(customComponentFactory(hrefRef, contextMenuParamsRef));
356386
newController.addEventListener(SELECTION_EVENT, setSelectedIds);
357387
return newController;
388+
// Controller is created once; context menu handlers read latest params via ref.
389+
// eslint-disable-next-line react-hooks/exhaustive-deps
358390
}, []);
359391

360392
const resourcePaths = React.useMemo(() => {

‎src/gitops/components/appset/graph/ApplicationSetGraphView.tsx‎

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,8 @@ export const ApplicationSetGraphView: React.FC<{
432432
};
433433
newController.fromModel(modelWithLayout, false);
434434
return newController;
435+
// Controller is recreated only when the ApplicationSet identity changes.
436+
// eslint-disable-next-line react-hooks/exhaustive-deps
435437
}, [applicationSetUid]);
436438

437439
// Parts of the logic to refresh and update the graph is similar to application graph view.
@@ -492,12 +494,11 @@ export const ApplicationSetGraphView: React.FC<{
492494
prevViewType.current = expandGroups;
493495
}
494496
// Register new layout factory
495-
if (applicationSet.status?.applicationStatus?.length > 0) {
496-
adjustedExpansionSetting = treeViewLayout === TreeViewLayout.OWNER_REFERENCE_LAYOUT;
497-
} else {
498-
adjustedExpansionSetting = true;
499-
}
500-
controller.registerLayoutFactory(customLayoutFactory(adjustedExpansionSetting));
497+
const expansionSetting =
498+
applicationSet.status?.applicationStatus?.length > 0
499+
? treeViewLayout === TreeViewLayout.OWNER_REFERENCE_LAYOUT
500+
: true;
501+
controller.registerLayoutFactory(customLayoutFactory(expansionSetting));
501502

502503
const hasCollapsedStepGroups = collapsedStepGroupsRef.current.size > 0;
503504

@@ -538,7 +539,15 @@ export const ApplicationSetGraphView: React.FC<{
538539
});
539540
}
540541
}
541-
}, [controller, nodes, initialEdges, treeViewLayout, applications, expandGroups]);
542+
}, [
543+
controller,
544+
nodes,
545+
initialEdges,
546+
treeViewLayout,
547+
applications,
548+
expandGroups,
549+
applicationSet.status?.applicationStatus?.length,
550+
]);
542551

543552
const previousNodeCountRef = React.useRef<number>(0);
544553
const previousNodeIdsRef = React.useRef<string>('');
@@ -732,6 +741,11 @@ export const ApplicationSetGraphView: React.FC<{
732741
}
733742
}
734743

744+
const structuralNodes = React.useMemo(
745+
() => nodes.filter((n) => n.type !== 'filler-node'),
746+
[nodes],
747+
);
748+
735749
// Effect to handle initial collapse of step-groups after first render
736750
// This runs after Dagre has laid out the expanded graph, then collapses
737751
React.useEffect(() => {
@@ -779,13 +793,7 @@ export const ApplicationSetGraphView: React.FC<{
779793
graph.layout();
780794
});
781795
}
782-
}, [
783-
controller,
784-
nodes.filter((n) => n.type !== 'filler-node'),
785-
treeViewLayout,
786-
expandedStepGroups,
787-
expandGroups,
788-
]);
796+
}, [controller, structuralNodes, initialNodes, treeViewLayout, expandedStepGroups, expandGroups]);
789797
return (
790798
<TopologyView
791799
className="gitops-topology-view"

‎src/gitops/components/imageupdater/ImageUpdaterDetailsTab.tsx‎

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ const ImageUpdaterDetailsTab: React.FC<ImageUpdaterDetailsTabProps> = ({ obj })
3030

3131
const status = obj.status || {};
3232
const readyCondition = status.conditions?.find((c) => c.type === 'Ready');
33-
const readyLabel = readyCondition
34-
? readyCondition.status === 'True' ? t('True') : t('False')
35-
: '-';
33+
let readyLabel = '-';
34+
if (readyCondition) {
35+
readyLabel = readyCondition.status === 'True' ? t('True') : t('False');
36+
}
3637

3738
return (
3839
<>
@@ -81,22 +82,14 @@ const ImageUpdaterDetailsTab: React.FC<ImageUpdaterDetailsTabProps> = ({ obj })
8182
title={t('Last Checked At')}
8283
help={t('When the controller last checked for image updates.')}
8384
>
84-
{status.lastCheckedAt ? (
85-
<Timestamp timestamp={status.lastCheckedAt} />
86-
) : (
87-
'-'
88-
)}
85+
{status.lastCheckedAt ? <Timestamp timestamp={status.lastCheckedAt} /> : '-'}
8986
</DetailsDescriptionGroup>
9087

9188
<DetailsDescriptionGroup
9289
title={t('Last Updated At')}
9390
help={t('When the controller last performed an image update.')}
9491
>
95-
{status.lastUpdatedAt ? (
96-
<Timestamp timestamp={status.lastUpdatedAt} />
97-
) : (
98-
'-'
99-
)}
92+
{status.lastUpdatedAt ? <Timestamp timestamp={status.lastUpdatedAt} /> : '-'}
10093
</DetailsDescriptionGroup>
10194

10295
<DetailsDescriptionGroup

0 commit comments

Comments
 (0)