fix(ui): show operation state on applications list page (cherry-pick release-3.5)
Project: argoproj/argo-cd | Language: Go | Concepts: api-design, data-structures, testing-strategy | Difficulty: mid
Link: https://github.com/argoproj/argo-cd/pull/29344
The problem
The Argo CD UI applications list page could not reliably display whether an application was currently undergoing a sync operation. The backend API did not expose a lightweight marker for the presence of an operation sync, and requesting the full sync operation object would include large, unnecessary fields (like resources and manifests), making list/watch API payloads much larger than needed.
Why it's non-trivial
A naive fix would simply include the entire sync operation object in the list payload. However, this object can be very large (containing manifests, resources, etc.), which is wasteful and can degrade performance for list/watch endpoints. The UI only needs to know if a sync operation is present—not its full details.
The approach
1. Project only a presence marker for sync operations (backend):
"status.operationState.operation.sync": func(app *v1alpha1.Application) any {
- if app.Status.OperationState != nil {
- return app.Status.OperationState.SyncResult
+ if app.Status.OperationState != nil && app.Status.OperationState.Operation.Sync != nil {
+ // The UI only needs the field's presence to classify the operation type,
+ // so project an empty object as a pure presence marker and drop the
+ // potentially large fields such as resources, manifests and sources to
+ // keep the list/watch payload small.
+ return &v1alpha1.SyncOperation{}
}
return nil
},
Annotation:
This change ensures that, if a sync operation is present, the API returns an empty SyncOperation object (i.e., {}) instead of the full object. This acts as a lightweight presence marker for the UI, preventing large payloads.
2. Update the UI to request the new field:
const APP_FIELDS = [
'status.sync.revision',
'status.health',
'status.operationState.phase',
+ 'status.operationState.operation.sync',
'status.operationState.startedAt',
'status.operationState.finishedAt'
];
Annotation:
The UI now explicitly requests the status.operationState.operation.sync field, allowing it to display operation state based on the presence of this field.
3. Add unit tests to verify correct field projection and absence of large payloads:
func TestProcessApplicationListField_OperationStateOperationSync(t *testing.T) {
// ...
list := v1alpha1.ApplicationList{
Items: []v1alpha1.Application{{Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Operation: v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{
Revision: "abc",
Resources: []v1alpha1.SyncOperationResource{{Group: "apps", Kind: "Deployment", Name: "web"}},
Manifests: []string{"apiVersion: v1\nkind: ConfigMap"},
}},
SyncResult: &v1alpha1.SyncOperationResult{Revision: "def"},
},
}}},
}
// ...
syncMap, ok, err := unstructured.NestedMap(item, "status", "operationState", "operation", "sync")
require.NoError(t, err)
require.True(t, ok)
// The field is a pure presence marker: it must be an empty object, with none
// of the operation's payload (revision, resources, manifests, sources).
require.Empty(t, syncMap)
}
Annotation:
This test checks that, even if the underlying sync operation has large fields, the projected sync field in the list response is always an empty object (presence marker only).
From the review thread
No notable review discussion on this PR.
Takeaways
- Minimize API payload size by projecting only the data required for the client’s use case, especially on list/watch endpoints.
- Use presence markers (e.g., empty objects) to signal state when the client only needs to know if something exists, not its full details.
- Always add targeted tests to verify that sensitive or large fields are not unintentionally exposed in slimmed-down API responses.