Add two features to improve application observability in the dashboard:
1. WorkloadsReady condition on Application status
- Query WorkloadMonitor resources to determine if all pods are running
- Expose WorkloadsReady as a separate condition alongside Ready
- Ready continues to reflect HelmRelease state only (no override) to
preserve backward compatibility with tooling and avoid false-negatives
during normal startup windows when pods are still coming up
- Handle three states: operational, not operational, unknown (pending)
- Fail-open on WorkloadMonitor query errors
- Integrate with Application Watch to emit MODIFIED events on
WorkloadMonitor changes (with initial-events-end safety)
- Register cozystack.io/v1alpha1 types in API server scheme
2. Events tab in dashboard
- Show Kubernetes Events scoped to application namespace
- Use status.namespace for Tenant applications
- Include both lastTimestamp and eventTime columns for K8s compat
3. Bug fix: WorkloadMonitor Operational status persistence
- Operational was written to stale 'monitor' instead of 'fresh'
inside RetryOnConflict, so it was never persisted to the cluster
Closes #2359
Closes #2360
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
60 lines
1.7 KiB
Go
60 lines
1.7 KiB
Go
package dashboard
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestEventsTab_Structure(t *testing.T) {
|
|
tab := eventsTab("PostgreSQL")
|
|
|
|
if tab["key"] != "events" {
|
|
t.Errorf("expected key=events, got %v", tab["key"])
|
|
}
|
|
if tab["label"] != "Events" {
|
|
t.Errorf("expected label=Events, got %v", tab["label"])
|
|
}
|
|
|
|
children, ok := tab["children"].([]any)
|
|
if !ok || len(children) != 1 {
|
|
t.Fatal("expected exactly 1 child in events tab")
|
|
}
|
|
|
|
table, ok := children[0].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("child is not a map")
|
|
}
|
|
if table["type"] != "EnrichedTable" {
|
|
t.Errorf("expected type=EnrichedTable, got %v", table["type"])
|
|
}
|
|
|
|
data, ok := table["data"].(map[string]any)
|
|
if !ok {
|
|
t.Fatal("table data is not a map")
|
|
}
|
|
if data["id"] != "events-table" {
|
|
t.Errorf("expected id=events-table, got %v", data["id"])
|
|
}
|
|
if data["fetchUrl"] != "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/events" {
|
|
t.Errorf("unexpected fetchUrl for non-Tenant: %v", data["fetchUrl"])
|
|
}
|
|
if data["customizationId"] != "factory-details-events" {
|
|
t.Errorf("expected customizationId=factory-details-events, got %v", data["customizationId"])
|
|
}
|
|
|
|
pathToItems, ok := data["pathToItems"].([]any)
|
|
if !ok || len(pathToItems) != 1 || pathToItems[0] != "items" {
|
|
t.Errorf("expected pathToItems=[items], got %v", data["pathToItems"])
|
|
}
|
|
}
|
|
|
|
func TestEventsTab_TenantUsesStatusNamespace(t *testing.T) {
|
|
tab := eventsTab("Tenant")
|
|
children := tab["children"].([]any)
|
|
table := children[0].(map[string]any)
|
|
data := table["data"].(map[string]any)
|
|
|
|
expectedURL := "/api/clusters/{2}/k8s/api/v1/namespaces/{reqsJsonPath[0]['.status.namespace']}/events"
|
|
if data["fetchUrl"] != expectedURL {
|
|
t.Errorf("expected Tenant fetchUrl to use status.namespace, got %v", data["fetchUrl"])
|
|
}
|
|
}
|