fix(ui): use hydrateTo branch name when set (#29554)
Project: argoproj/argo-cd | Language: Go/TypeScript (React) | Concepts: refactoring, testing-strategy | Difficulty: junior
Link: https://github.com/argoproj/argo-cd/pull/29562
The problem
When the spec.sourceHydrator.hydrateTo.targetBranch field is set in an Argo CD Application, the UI panel for Source Hydrator did not display this branch as the destination. Instead, it always showed the sync source branch, which could confuse users about where hydration results would be pushed.
Why it's non-trivial
A naive fix might just swap out the branch name in one place, but the destination branch appears in multiple hydration status phases. The logic for which branch to show (hydrateTo vs. sync source) must be consistent everywhere, and the fallback behavior (when hydrateTo is unset) must be preserved. Tests must also cover both cases.
The approach
1. Centralize destination branch resolution with a new helper
+// Destination of a hydration push: hydrateTo.targetBranch when set, otherwise the sync source branch.
+export function getAppHydrateToSource(sourceHydrator?: appModels.SourceHydrator): appModels.ApplicationSource {
+ const syncSource = getAppHydratorSyncSource(sourceHydrator);
+ return {
+ ...syncSource,
+ targetRevision: sourceHydrator?.hydrateTo?.targetBranch || syncSource.targetRevision
+ };
+}
This function ensures that if hydrateTo.targetBranch is set, it is used as the destination branch. Otherwise, it falls back to the sync source branch. This encapsulates the logic in one place, reducing duplication and potential for error.
2. Update all hydration status UI rendering to use the new helper
- const syncSource = getAppHydratorSyncSource(sourceHydrator);
+ const hydrateToSource = getAppHydrateToSource(sourceHydrator);
Replaces the previous sync source resolution with the new helper, ensuring the correct branch is used everywhere.
- <Revision repoUrl={syncSource.repoURL} revision={hydratedCommit}>
- {syncSource.targetRevision + ' (' + hydratedCommit.substr(0, 7) + ')'}
+ <Revision repoUrl={hydrateToSource.repoURL} revision={hydratedCommit}>
+ {hydrateToSource.targetRevision + ' (' + hydratedCommit.substr(0, 7) + ')'}
All places in the hydration status message now use the resolved hydrateTo branch (or fallback) for display.
3. Add targeted unit tests for both cases
+describe('getAppHydrateToSource', () => {
+ it('uses hydrateTo.targetBranch when set', () => {
+ expect(
+ getAppHydrateToSource({
+ drySource: {repoURL: 'https://github.com/example/dry.git', targetRevision: 'main', path: 'in'},
+ syncSource: {repoURL: 'https://github.com/example/hydrated.git', targetBranch: 'env/test', path: 'out'},
+ hydrateTo: {targetBranch: 'env/test-hydrate'}
+ })
+ ).toEqual({
+ repoURL: 'https://github.com/example/hydrated.git',
+ targetRevision: 'env/test-hydrate',
+ path: 'out'
+ });
+ });
+
+ it('falls back to the sync source branch when hydrateTo is unset', () => {
+ expect(
+ getAppHydrateToSource({
+ drySource: {repoURL: 'https://github.com/example/dry.git', targetRevision: 'main', path: 'in'},
+ syncSource: {targetBranch: 'env/test', path: 'out'}
+ })
+ ).toEqual({
+ repoURL: 'https://github.com/example/dry.git',
+ targetRevision: 'env/test',
+ path: 'out'
+ });
+ });
+});
These tests verify the new helper works for both the override and fallback cases.
+describe('hydrationStatusMessage', () => {
+ it('shows hydrateTo as the destination while hydrating', () => {
+ const html = renderMarkup(
+ hydrationStatusMessage({
+ status: {
+ sourceHydrator: {
+ currentOperation: {
+ phase: 'Hydrating',
+ sourceHydrator: {
+ drySource: {repoURL: 'https://github.com/example/dry.git', targetRevision: 'main'},
+ syncSource: {targetBranch: 'env/test', path: 'out'},
+ hydrateTo: {targetBranch: 'env/test-hydrate'}
+ }
+ }
+ }
+ }
+ } as Application)
+ );
+ expect(html).toContain('env/test-hydrate');
+ expect(html).not.toContain('env/test)');
+ expect(html).not.toMatch(/>env\/test</);
+ });
+});
This test ensures the rendered status message displays the correct destination branch.
From the review thread
No notable review discussion on this PR.
Takeaways
- Encapsulate conditional logic in a helper function: When UI logic depends on multiple possible sources of truth (e.g., override or fallback fields), use a single function to resolve the value everywhere, reducing duplication and errors.
- Update all affected UI code paths: When fixing a display bug, ensure every code path that renders the affected value uses the new logic.
- Write targeted tests for both override and fallback: Always add tests that cover both the "override set" and "fallback" scenarios to prevent regressions.