From 9269d175bdffd5628ba9fbb3253a52ac01306df7 Mon Sep 17 00:00:00 2001
From: Anna Vovchenko <avovchenko@gitlab.com>
Date: Thu, 30 Nov 2023 10:11:34 +0000
Subject: [PATCH] Fix Kubernetes cluster health badge

Currently, we only calculate cluster health once.
With the addition of the watch_api, we need a way to update it
when new data is received.

Changelog: fixed
---
 .../components/kubernetes_overview.vue        | 15 ++++++++++++---
 .../components/kubernetes_pods.vue            |  7 ++++---
 .../components/kubernetes_status_bar.vue      |  2 +-
 .../components/kubernetes_summary.vue         |  4 +---
 .../components/kubernetes_tabs.vue            |  2 +-
 locale/gitlab.pot                             |  2 +-
 .../environments/kubernetes_overview_spec.js  | 19 +++++++++++++++++--
 .../environments/kubernetes_pods_spec.js      | 10 ++++++++--
 .../environments/kubernetes_summary_spec.js   |  4 ++--
 .../environments/kubernetes_tabs_spec.js      |  7 ++++---
 10 files changed, 51 insertions(+), 21 deletions(-)

diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index 36cce29d6247f..d5a7b43c95319 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -43,7 +43,7 @@ export default {
     return {
       isVisible: false,
       error: '',
-      hasFailedState: false,
+      failedState: {},
       podsLoading: false,
       workloadTypesLoading: false,
     };
@@ -78,6 +78,9 @@ export default {
 
       return this.hasFailedState ? 'error' : 'success';
     },
+    hasFailedState() {
+      return Object.values(this.failedState).some((item) => item);
+    },
   },
   methods: {
     toggleCollapse() {
@@ -86,6 +89,12 @@ export default {
     onClusterError(message) {
       this.error = message;
     },
+    onUpdateFailedState(event) {
+      this.failedState = {
+        ...this.failedState,
+        ...event,
+      };
+    },
   },
   i18n: {
     collapse: __('Collapse'),
@@ -126,14 +135,14 @@ export default {
           class="gl-mb-5"
           @cluster-error="onClusterError"
           @loading="podsLoading = $event"
-          @failed="hasFailedState = true" />
+          @update-failed-state="onUpdateFailedState" />
         <kubernetes-tabs
           :configuration="k8sAccessConfiguration"
           :namespace="namespace"
           class="gl-mb-5"
           @cluster-error="onClusterError"
           @loading="workloadTypesLoading = $event"
-          @failed="hasFailedState = true"
+          @update-failed-state="onUpdateFailedState"
       /></template>
     </gl-collapse>
   </div>
diff --git a/app/assets/javascripts/environments/components/kubernetes_pods.vue b/app/assets/javascripts/environments/components/kubernetes_pods.vue
index 743159d62563f..2015355f7940b 100644
--- a/app/assets/javascripts/environments/components/kubernetes_pods.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_pods.vue
@@ -82,9 +82,10 @@ export default {
   methods: {
     countPodsByPhase(phase) {
       const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase);
-      if (phase === PHASE_FAILED && filteredPods.length) {
-        this.$emit('failed');
-      }
+
+      const hasFailedState = Boolean(phase === PHASE_FAILED && filteredPods.length);
+      this.$emit('update-failed-state', { pods: hasFailedState });
+
       return filteredPods.length;
     },
   },
diff --git a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
index 8ecb61711ce28..20ed67f6bd9cf 100644
--- a/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_status_bar.vue
@@ -153,7 +153,7 @@ export default {
     },
   },
   i18n: {
-    healthLabel: s__('Environment|Environment health'),
+    healthLabel: s__('Environment|Environment status'),
     syncStatusLabel: s__('Environment|Sync status'),
   },
   badgeContainerClasses: 'gl-display-flex gl-align-items-center gl-flex-shrink-0 gl-mr-3 gl-mb-2',
diff --git a/app/assets/javascripts/environments/components/kubernetes_summary.vue b/app/assets/javascripts/environments/components/kubernetes_summary.vue
index 1f4e91afe3577..2912fd8f4d88f 100644
--- a/app/assets/javascripts/environments/components/kubernetes_summary.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_summary.vue
@@ -140,9 +140,7 @@ export default {
         return workloadType.items?.failed?.length > 0;
       });
 
-      if (failed) {
-        this.$emit('failed');
-      }
+      this.$emit('update-failed-state', { summary: failed });
     },
   },
   i18n: {
diff --git a/app/assets/javascripts/environments/components/kubernetes_tabs.vue b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
index 60b36596ef3f6..0d80b1fd79712 100644
--- a/app/assets/javascripts/environments/components/kubernetes_tabs.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_tabs.vue
@@ -140,7 +140,7 @@ export default {
       :namespace="namespace"
       :configuration="configuration"
       @loading="$emit('loading', $event)"
-      @failed="$emit('failed')"
+      @update-failed-state="$emit('update-failed-state', $event)"
       @cluster-error="$emit('cluster-error', $event)"
     />
 
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 74170c6e5016c..453b983fddba2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18985,7 +18985,7 @@ msgstr ""
 msgid "Environment|Deployments"
 msgstr ""
 
-msgid "Environment|Environment health"
+msgid "Environment|Environment status"
 msgstr ""
 
 msgid "Environment|External IP"
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
index 12689df586fe0..9f4a7518c473d 100644
--- a/spec/frontend/environments/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -149,14 +149,14 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
     });
 
     it('sets `clusterHealthStatus` as error when pods emitted a failure', async () => {
-      findKubernetesPods().vm.$emit('failed');
+      findKubernetesPods().vm.$emit('update-failed-state', { pods: true });
       await nextTick();
 
       expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
     });
 
     it('sets `clusterHealthStatus` as error when workload types emitted a failure', async () => {
-      findKubernetesTabs().vm.$emit('failed');
+      findKubernetesTabs().vm.$emit('update-failed-state', { summary: true });
       await nextTick();
 
       expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
@@ -165,6 +165,21 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
     it('sets `clusterHealthStatus` as success when data is loaded and no failures where emitted', () => {
       expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
     });
+
+    it('sets `clusterHealthStatus` as success after state update if there are no failures', async () => {
+      findKubernetesTabs().vm.$emit('update-failed-state', { summary: true });
+      findKubernetesTabs().vm.$emit('update-failed-state', { pods: true });
+      await nextTick();
+      expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
+
+      findKubernetesTabs().vm.$emit('update-failed-state', { summary: false });
+      await nextTick();
+      expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error');
+
+      findKubernetesTabs().vm.$emit('update-failed-state', { pods: false });
+      await nextTick();
+      expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success');
+    });
   });
 
   describe('on cluster error', () => {
diff --git a/spec/frontend/environments/kubernetes_pods_spec.js b/spec/frontend/environments/kubernetes_pods_spec.js
index 2952e0c68c219..6c3e49e4d8a00 100644
--- a/spec/frontend/environments/kubernetes_pods_spec.js
+++ b/spec/frontend/environments/kubernetes_pods_spec.js
@@ -90,11 +90,17 @@ describe('~/environments/components/kubernetes_pods.vue', () => {
       ]);
     });
 
-    it('emits a failed event when there are failed pods', async () => {
+    it('emits a update-failed-state event for each pod', async () => {
       createWrapper();
       await waitForPromises();
 
-      expect(wrapper.emitted('failed')).toHaveLength(1);
+      expect(wrapper.emitted('update-failed-state')).toHaveLength(4);
+      expect(wrapper.emitted('update-failed-state')).toEqual([
+        [{ pods: false }],
+        [{ pods: false }],
+        [{ pods: false }],
+        [{ pods: true }],
+      ]);
     });
   });
 
diff --git a/spec/frontend/environments/kubernetes_summary_spec.js b/spec/frontend/environments/kubernetes_summary_spec.js
index efabd766001d8..0d448d0b6af0d 100644
--- a/spec/frontend/environments/kubernetes_summary_spec.js
+++ b/spec/frontend/environments/kubernetes_summary_spec.js
@@ -107,8 +107,8 @@ describe('~/environments/components/kubernetes_summary.vue', () => {
       );
     });
 
-    it('emits a failed event when there are failed workload types', () => {
-      expect(wrapper.emitted('failed')).toHaveLength(1);
+    it('emits a update-failed-state event when there are failed workload types', () => {
+      expect(wrapper.emitted('update-failed-state')).toEqual([[{ summary: true }]]);
     });
 
     it('emits an error message when gets an error from the cluster_client API', async () => {
diff --git a/spec/frontend/environments/kubernetes_tabs_spec.js b/spec/frontend/environments/kubernetes_tabs_spec.js
index fecd6d2a8ee1a..bf029ad6a810c 100644
--- a/spec/frontend/environments/kubernetes_tabs_spec.js
+++ b/spec/frontend/environments/kubernetes_tabs_spec.js
@@ -179,9 +179,10 @@ describe('~/environments/components/kubernetes_tabs.vue', () => {
       expect(wrapper.emitted('loading')[1]).toEqual([false]);
     });
 
-    it('emits a failed event when gets it from the component', () => {
-      findKubernetesSummary().vm.$emit('failed');
-      expect(wrapper.emitted('failed')).toHaveLength(1);
+    it('emits a state update event when gets it from the component', () => {
+      const eventData = { summary: true };
+      findKubernetesSummary().vm.$emit('update-failed-state', eventData);
+      expect(wrapper.emitted('update-failed-state')).toEqual([[eventData]]);
     });
   });
 });
-- 
GitLab