From 058e7216a3f7163f33c9c40b707199f3e6ebf7f7 Mon Sep 17 00:00:00 2001
From: Anna Vovchenko <avovchenko@gitlab.com>
Date: Wed, 24 Jul 2024 19:10:29 +0000
Subject: [PATCH] Add delete pod functionality

Add mutation, actions dropdown to the pods table,
confirmation modal,
error alert and success toast.

Changelog: added
---
 .../kubernetes/delete_pod_modal.vue           |  9 +++-
 .../kubernetes/kubernetes_overview.vue        |  6 ++-
 .../components/kubernetes/kubernetes_pods.vue |  3 +-
 .../kubernetes/kubernetes_status_bar.vue      |  1 -
 .../components/workload_details.vue           | 24 +++++++++-
 locale/gitlab.pot                             |  8 ++--
 .../kubernetes/kubernetes_overview_spec.js    | 10 ++++
 .../kubernetes/kubernetes_pods_spec.js        |  3 +-
 .../graphql/resolvers/kubernetes_spec.js      |  4 +-
 .../components/workload_details_spec.js       | 46 ++++++++++++++++++-
 .../components/workload_table_spec.js         |  2 +-
 11 files changed, 99 insertions(+), 17 deletions(-)

diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/delete_pod_modal.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/delete_pod_modal.vue
index 35a97b120cf10..f758eb835f75d 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/delete_pod_modal.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/delete_pod_modal.vue
@@ -76,7 +76,7 @@ export default {
       'Environments|Are you sure you want to delete %{podName}? This action cannot be undone.',
     ),
     buttonPrimary: s__('Environments|Delete pod'),
-    buttonCancel: s__('Environments|Cancel'),
+    buttonCancel: __('Cancel'),
     success: s__('Environments|Pod deleted successfully'),
     error: __('Error: '),
   },
@@ -84,7 +84,12 @@ export default {
 };
 </script>
 <template>
-  <gl-modal v-model="visible" :modal-id="$options.DELETE_POD_MODAL_ID" @hide="hideModal">
+  <gl-modal
+    v-model="visible"
+    :modal-id="$options.DELETE_POD_MODAL_ID"
+    :aria-label="$options.i18n.buttonPrimary"
+    @hide="hideModal"
+  >
     <template #modal-title>
       <gl-sprintf :message="$options.i18n.title">
         <template #podName>
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
index 5b1dde88e9449..22576ef69f190 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue
@@ -106,6 +106,7 @@ export default {
       fluxApiError: '',
       selectedItem: {},
       showDetailsDrawer: false,
+      focusedElement: null,
       podToDelete: {},
     };
   },
@@ -173,6 +174,7 @@ export default {
     openDetailsDrawer(item) {
       this.selectedItem = item;
       this.showDetailsDrawer = true;
+      this.focusedElement = document.activeElement;
       this.$nextTick(() => {
         this.$refs.drawer?.$el?.querySelector('button')?.focus();
       });
@@ -181,7 +183,7 @@ export default {
       this.showDetailsDrawer = false;
       this.selectedItem = {};
       this.$nextTick(() => {
-        this.$refs.status_bar?.$refs?.flux_status_badge?.$el?.focus();
+        this.focusedElement?.focus();
       });
     },
     onDeletePod(pod) {
@@ -261,7 +263,7 @@ export default {
         </h2>
       </template>
       <template #default>
-        <workload-details v-if="hasSelectedItem" :item="selectedItem" />
+        <workload-details v-if="hasSelectedItem" :item="selectedItem" @delete-pod="onDeletePod" />
       </template>
     </gl-drawer>
   </div>
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
index 0ec1ad6a57f96..9f484365a7945 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_pods.vue
@@ -48,8 +48,9 @@ export default {
               actions: [
                 {
                   name: 'delete-pod',
-                  text: s__('KubernetesDashboard|Delete Pod'),
+                  text: s__('KubernetesDashboard|Delete pod'),
                   icon: 'remove',
+                  variant: 'danger',
                   class: '!gl-text-red-500',
                 },
               ],
diff --git a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
index 5977c02d6f471..00512f635e3d7 100644
--- a/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
+++ b/app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_status_bar.vue
@@ -227,7 +227,6 @@ export default {
       <template v-else>
         <gl-badge
           :id="fluxBadgeId"
-          ref="flux_status_badge"
           :icon="syncStatusBadge.icon"
           :variant="syncStatusBadge.variant"
           data-testid="sync-badge"
diff --git a/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue b/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
index cba723e806e4f..14c1555bad3c6 100644
--- a/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
+++ b/app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlBadge, GlTruncate } from '@gitlab/ui';
+import { GlBadge, GlTruncate, GlButton, GlTooltipDirective } from '@gitlab/ui';
 import { stringify } from 'yaml';
 import { s__ } from '~/locale';
 import PodLogsButton from '~/environments/environment_details/components/kubernetes/pod_logs_button.vue';
@@ -10,9 +10,13 @@ export default {
   components: {
     GlBadge,
     GlTruncate,
+    GlButton,
     WorkloadDetailsItem,
     PodLogsButton,
   },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
   props: {
     item: {
       type: Object,
@@ -44,6 +48,9 @@ export default {
     hasContainers() {
       return Boolean(this.item.containers);
     },
+    hasActions() {
+      return Boolean(this.item.actions?.length);
+    },
   },
   methods: {
     getLabelBadgeText([key, value]) {
@@ -65,6 +72,7 @@ export default {
   },
   i18n: {
     name: s__('KubernetesDashboard|Name'),
+    actions: s__('KubernetesDashboard|Actions'),
     kind: s__('KubernetesDashboard|Kind'),
     labels: s__('KubernetesDashboard|Labels'),
     status: s__('KubernetesDashboard|Status'),
@@ -82,6 +90,20 @@ export default {
     <workload-details-item :label="$options.i18n.name">
       <span class="gl-break-anywhere"> {{ item.name }}</span>
     </workload-details-item>
+    <workload-details-item v-if="hasActions" :label="$options.i18n.actions">
+      <span v-for="action of item.actions" :key="action.name">
+        <gl-button
+          v-gl-tooltip
+          :title="action.text"
+          :aria-label="action.text"
+          :variant="action.variant"
+          :icon="action.icon"
+          category="secondary"
+          class="gl-mr-3"
+          @click="$emit(action.name, item)"
+        />
+      </span>
+    </workload-details-item>
     <workload-details-item :label="$options.i18n.kind">
       {{ item.kind }}
     </workload-details-item>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bc0dec17a5694..ff9d65da9f70f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20480,9 +20480,6 @@ msgstr ""
 msgid "Environments|Auto stops %{autoStopAt}"
 msgstr ""
 
-msgid "Environments|Cancel"
-msgstr ""
-
 msgid "Environments|Clean up"
 msgstr ""
 
@@ -30320,6 +30317,9 @@ msgstr ""
 msgid "Kubernetes deployment not found"
 msgstr ""
 
+msgid "KubernetesDashboard|Actions"
+msgstr ""
+
 msgid "KubernetesDashboard|Age"
 msgstr ""
 
@@ -30353,7 +30353,7 @@ msgstr ""
 msgid "KubernetesDashboard|Dashboard"
 msgstr ""
 
-msgid "KubernetesDashboard|Delete Pod"
+msgid "KubernetesDashboard|Delete pod"
 msgstr ""
 
 msgid "KubernetesDashboard|Deployment"
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
index 44d7fc4f6d66d..c7560501a629b 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_overview_spec.js
@@ -377,6 +377,16 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
 
         expect(findDeletePodModal().props('pod')).toEqual(podToDelete);
       });
+
+      it('provides correct pod when emitted from the details drawer', async () => {
+        const podToDelete = mockPodsTableItems[1];
+        findKubernetesTabs().vm.$emit('show-resource-details', mockPodsTableItems[1]);
+        await nextTick();
+        findWorkloadDetails().vm.$emit('delete-pod', podToDelete);
+        await nextTick();
+
+        expect(findDeletePodModal().props('pod')).toEqual(podToDelete);
+      });
     });
 
     describe('on child component error', () => {
diff --git a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_pods_spec.js b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_pods_spec.js
index fd5f26f96eaa6..23ac293351b89 100644
--- a/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_pods_spec.js
+++ b/spec/frontend/environments/environment_details/components/kubernetes/kubernetes_pods_spec.js
@@ -106,8 +106,9 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_po
       const actions = [
         {
           name: 'delete-pod',
-          text: 'Delete Pod',
+          text: 'Delete pod',
           icon: 'remove',
+          variant: 'danger',
           class: '!gl-text-red-500',
         },
       ];
diff --git a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
index a3e8367263760..59d82f4132c12 100644
--- a/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
+++ b/spec/frontend/environments/graphql/resolvers/kubernetes_spec.js
@@ -374,9 +374,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
   });
 
   describe('deleteKubernetesPod', () => {
-    const mockPodsDeleteFn = jest.fn().mockImplementation(() => {
-      return Promise.resolve({ errors: [] });
-    });
+    const mockPodsDeleteFn = jest.fn().mockResolvedValue({ errors: [] });
     const podToDelete = 'my-pod';
 
     it('should request delete pod API from the cluster_client library', async () => {
diff --git a/spec/frontend/kubernetes_dashboard/components/workload_details_spec.js b/spec/frontend/kubernetes_dashboard/components/workload_details_spec.js
index 2447bf0f5d594..cbc9483ffac40 100644
--- a/spec/frontend/kubernetes_dashboard/components/workload_details_spec.js
+++ b/spec/frontend/kubernetes_dashboard/components/workload_details_spec.js
@@ -1,5 +1,5 @@
 import { shallowMount } from '@vue/test-utils';
-import { GlBadge, GlTruncate } from '@gitlab/ui';
+import { GlBadge, GlTruncate, GlButton } from '@gitlab/ui';
 import WorkloadDetails from '~/kubernetes_dashboard/components/workload_details.vue';
 import WorkloadDetailsItem from '~/kubernetes_dashboard/components/workload_details_item.vue';
 import { WORKLOAD_STATUS_BADGE_VARIANTS } from '~/kubernetes_dashboard/constants';
@@ -25,6 +25,8 @@ const findAllBadges = () => wrapper.findAllComponents(GlBadge);
 const findBadge = (at) => findAllBadges().at(at);
 const findAllPodLogsButtons = () => wrapper.findAllComponents(PodLogsButton);
 const findPodLogsButton = (at) => findAllPodLogsButtons().at(at);
+const findAllButtons = () => wrapper.findAllComponents(GlButton);
+const findButton = (at) => findAllButtons().at(at);
 
 describe('Workload details component', () => {
   describe('when minimal fields are provided', () => {
@@ -74,6 +76,48 @@ describe('Workload details component', () => {
       expect(findWorkloadDetailsItem(index).props('collapsible')).toBe(true);
     });
 
+    describe('when actions are provided', () => {
+      const actions = [
+        {
+          name: 'delete-pod',
+          text: 'Delete pod',
+          icon: 'remove',
+          variant: 'danger',
+        },
+      ];
+      const mockTableItemsWithActions = {
+        ...mockPodsTableItems[0],
+        actions,
+      };
+
+      beforeEach(() => {
+        createWrapper(mockTableItemsWithActions);
+      });
+
+      it('renders a non-collapsible list item for containers', () => {
+        expect(findWorkloadDetailsItem(1).props('label')).toBe('Actions');
+        expect(findWorkloadDetailsItem(1).props('collapsible')).toBe(false);
+      });
+
+      it('renders a button for each action', () => {
+        expect(findAllButtons()).toHaveLength(1);
+      });
+
+      it.each(actions)('renders a button with the correct props', (action) => {
+        const currentIndex = actions.indexOf(action);
+
+        expect(findButton(currentIndex).props()).toMatchObject({
+          variant: action.variant,
+          icon: action.icon,
+        });
+
+        expect(findButton(currentIndex).attributes()).toMatchObject({
+          title: action.text,
+          'aria-label': action.text,
+        });
+      });
+    });
+
     describe('when containers are provided', () => {
       const mockTableItemsWithContainers = {
         ...mockPodsTableItems[0],
diff --git a/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js b/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js
index 8866b550a911e..4ac6035be126e 100644
--- a/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js
+++ b/spec/frontend/kubernetes_dashboard/components/workload_table_spec.js
@@ -157,7 +157,7 @@ describe('Workload table component', () => {
       });
 
       it('renders correct props for each dropdown', () => {
-        expect(findAllActionsDropdowns().at(0).attributes('title')).toEqual('Actions');
+        expect(findAllActionsDropdowns().at(0).attributes('title')).toBe('Actions');
         expect(findAllActionsDropdowns().at(0).props('items')).toMatchObject([
           {
             text: 'Delete Pod',
-- 
GitLab