From 3fb0db611b5faa615565d5cd9a839a33fac06a88 Mon Sep 17 00:00:00 2001
From: Julia Miocene <jmiocene@gitlab.com>
Date: Wed, 14 Feb 2024 01:33:03 +0000
Subject: [PATCH] Improve environments dashboard

Changelog: changed
---
 .../components/dashboard/dashboard.vue        | 58 ++++++-----
 .../components/dashboard/dashboard.vue        | 79 ++++++++-------
 .../__snapshots__/dashboard_spec.js.snap      | 99 -------------------
 .../components/dashboard_spec.js              | 92 +++++++++--------
 .../components/dashboard/dashboard_spec.js    | 80 ++++++++-------
 locale/gitlab.pot                             |  3 -
 6 files changed, 168 insertions(+), 243 deletions(-)
 delete mode 100644 ee/spec/frontend/environments_dashboard/components/__snapshots__/dashboard_spec.js.snap

diff --git a/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue b/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue
index 2d468583e80af..212155ea51850 100644
--- a/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue
+++ b/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue
@@ -96,6 +96,9 @@ export default {
         this.paginateDashboard(newPage);
       },
     },
+    showDashboard() {
+      return this.projects.length || this.isLoadingProjects;
+    },
     projectsPerPage() {
       return this.projectsPage.pageInfo.perPage;
     },
@@ -160,7 +163,7 @@ export default {
 </script>
 
 <template>
-  <div class="environments-dashboard">
+  <div v-if="showDashboard" class="environments-dashboard">
     <gl-modal
       :modal-id="$options.modalId"
       :title="$options.addProjectsModalHeader"
@@ -193,14 +196,17 @@ export default {
       />
     </gl-modal>
     <div class="page-title-holder flex-fill d-flex gl-align-items-center">
-      <h1 class="js-dashboard-title page-title gl-font-size-h-display text-nowrap flex-fill">
+      <h1
+        class="page-title gl-font-size-h-display text-nowrap flex-fill"
+        data-testid="dashboard-title"
+      >
         {{ $options.dashboardHeader }}
       </h1>
-      <gl-button v-gl-modal="$options.modalId" class="js-add-projects-button" variant="confirm">
+      <gl-button v-gl-modal="$options.modalId" data-testid="add-projects-button" variant="confirm">
         {{ $options.addProjectsButton }}
       </gl-button>
     </div>
-    <p class="mt-2 mb-4 js-page-limits-message">
+    <p class="gl-mt-3 gl-mb-6" data-testid="page-limits-message">
       <gl-sprintf :message="$options.informationText">
         <template #link="{ content }">
           <gl-link :href="environmentsDashboardHelpPath" target="_blank">
@@ -234,26 +240,30 @@ export default {
       </div>
 
       <gl-dashboard-skeleton v-else-if="isLoadingProjects" />
-
-      <gl-empty-state
-        v-else
-        :title="$options.emptyDashboardHeader"
-        :svg-path="emptyDashboardSvgPath"
-        :svg-height="150"
-      >
-        <template #description>
-          {{ $options.emptyDashboardDocs }}
-          <gl-link :href="emptyDashboardHelpPath" class="js-documentation-link">{{
-            $options.viewDocumentationButton
-          }}</gl-link
-          >.
-        </template>
-        <template #actions>
-          <gl-button v-gl-modal="$options.modalId" variant="confirm" class="js-add-projects-button">
-            {{ s__('ModalButton|Add projects') }}
-          </gl-button>
-        </template>
-      </gl-empty-state>
     </div>
   </div>
+  <gl-empty-state
+    v-else
+    :title="$options.emptyDashboardHeader"
+    :description="$options.emptyDashboardDocs"
+    :svg-path="emptyDashboardSvgPath"
+  >
+    <template #actions>
+      <gl-button
+        v-gl-modal="$options.modalId"
+        variant="confirm"
+        class="gl-mb-3 gl-mx-2"
+        data-testid="add-projects-button"
+      >
+        {{ $options.addProjectsButton }}
+      </gl-button>
+      <gl-button
+        :href="emptyDashboardHelpPath"
+        class="gl-mb-3 gl-mx-2"
+        data-testid="documentation-link"
+      >
+        {{ $options.viewDocumentationButton }}
+      </gl-button>
+    </template>
+  </gl-empty-state>
 </template>
diff --git a/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue b/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue
index 7baf0fa801c84..6032dcc6e8462 100644
--- a/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue
+++ b/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue
@@ -17,9 +17,18 @@ import ProjectSelector from '~/vue_shared/components/project_selector/project_se
 import DashboardProject from './project.vue';
 
 export default {
+  title: s__('OperationsDashboard|Operations Dashboard'),
   informationText: s__(
     'OperationsDashboard|The Operations and Environments dashboards share the same list of projects. When you add or remove a project from one, GitLab adds or removes the project from the other. %{linkStart}More information%{linkEnd}',
   ),
+  moreInformationButton: s__('OperationsDashboard|More information'),
+  addProjectsSubmitButton: s__('OperationsDashboard|Add projects'),
+  addProjectsModalHeader: s__('OperationsDashboard|Add projects'),
+  dashboardHeader: s__('OperationsDashboard|Operations Dashboard'),
+  emptyStateTitle: s__(`OperationsDashboard|Add a project to the dashboard`),
+  emptyStateDescription: s__(
+    `OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses.`,
+  ),
   components: {
     DashboardProject,
     GlDashboardSkeleton,
@@ -74,6 +83,9 @@ export default {
         this.setProjects(projects);
       },
     },
+    showDashboard() {
+      return this.projects.length || this.isLoadingProjects;
+    },
     isSearchingProjects() {
       return this.searchCount > 0;
     },
@@ -82,7 +94,7 @@ export default {
     },
     actionPrimary() {
       return {
-        text: s__('OperationsDashboard|Add projects'),
+        text: this.addProjectsSubmitButton,
         attributes: {
           disabled: this.okDisabled,
           variant: 'confirm',
@@ -135,10 +147,10 @@ export default {
 </script>
 
 <template>
-  <div class="operations-dashboard">
+  <div v-if="showDashboard" class="operations-dashboard">
     <gl-modal
       :modal-id="$options.modalId"
-      :title="s__('OperationsDashboard|Add projects')"
+      :title="$options.addProjectsModalHeader"
       :action-primary="actionPrimary"
       :action-cancel="$options.modal.actionCancel"
       data-testid="add-projects-modal"
@@ -161,8 +173,11 @@ export default {
     </gl-modal>
 
     <div class="page-title-holder flex-fill d-flex gl-align-items-center">
-      <h1 class="js-dashboard-title page-title gl-font-size-h-display text-nowrap flex-fill">
-        {{ s__('OperationsDashboard|Operations Dashboard') }}
+      <h1
+        class="page-title gl-font-size-h-display text-nowrap flex-fill"
+        data-testid="dashboard-title"
+      >
+        {{ $options.title }}
       </h1>
       <gl-button
         v-if="projects.length"
@@ -171,7 +186,7 @@ export default {
         category="primary"
         data-testid="add-projects-button"
       >
-        {{ s__('OperationsDashboard|Add projects') }}
+        {{ $options.addProjectsSubmitButton }}
       </gl-button>
     </div>
     <p class="gl-mt-2 gl-mb-4">
@@ -196,34 +211,30 @@ export default {
       </vue-draggable>
 
       <gl-dashboard-skeleton v-else-if="isLoadingProjects" />
-
-      <gl-empty-state
-        v-else
-        :title="s__(`OperationsDashboard|Add a project to the dashboard`)"
-        :svg-path="emptyDashboardSvgPath"
-        :svg-height="150"
-      >
-        <template #description>
-          {{
-            s__(
-              `OperationsDashboard|The operations dashboard provides a summary of each project's operational health, including pipeline and alert statuses.`,
-            )
-          }}
-          <gl-link :href="emptyDashboardHelpPath" data-testid="documentation-link">{{
-            s__('OperationsDashboard|More information')
-          }}</gl-link
-          >.
-        </template>
-        <template #actions>
-          <gl-button
-            v-gl-modal="$options.modalId"
-            variant="confirm"
-            data-testid="add-projects-button"
-          >
-            {{ s__('OperationsDashboard|Add projects') }}
-          </gl-button>
-        </template>
-      </gl-empty-state>
     </div>
   </div>
+  <gl-empty-state
+    v-else
+    :title="$options.emptyStateTitle"
+    :description="$options.emptyStateDescription"
+    :svg-path="emptyDashboardSvgPath"
+  >
+    <template #actions>
+      <gl-button
+        v-gl-modal="$options.modalId"
+        variant="confirm"
+        data-testid="add-projects-button"
+        class="gl-mb-3 gl-mx-2"
+      >
+        {{ $options.addProjectsSubmitButton }}
+      </gl-button>
+      <gl-button
+        :href="emptyDashboardHelpPath"
+        data-testid="documentation-link"
+        class="gl-mb-3 gl-mx-2"
+      >
+        {{ $options.moreInformationButton }}
+      </gl-button>
+    </template>
+  </gl-empty-state>
 </template>
diff --git a/ee/spec/frontend/environments_dashboard/components/__snapshots__/dashboard_spec.js.snap b/ee/spec/frontend/environments_dashboard/components/__snapshots__/dashboard_spec.js.snap
deleted file mode 100644
index d68cbec111ca3..0000000000000
--- a/ee/spec/frontend/environments_dashboard/components/__snapshots__/dashboard_spec.js.snap
+++ /dev/null
@@ -1,99 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`dashboard should match the snapshot 1`] = `
-<div
-  class="environments-dashboard"
->
-  <gl-modal-stub
-    arialabel=""
-    dismisslabel="Close"
-    modalclass=""
-    modalid="add-projects-modal"
-    ok-disabled="true"
-    ok-title="Add projects"
-    size="md"
-    title="Add projects"
-    titletag="h4"
-  >
-    <p>
-      This dashboard displays 3 environments per project, and is linked to the Operations Dashboard. When you add or remove a project from one dashboard, GitLab adds or removes the project from the other.
-      <gl-link-stub
-        href="/help/user/operations_dashboard/index.html"
-        target="_blank"
-      >
-        More information
-      </gl-link-stub>
-    </p>
-    <project-selector-stub
-      maxlistheight="402"
-      projectsearchresults=""
-      selectedprojects=""
-      totalresults="0"
-    />
-  </gl-modal-stub>
-  <div
-    class="d-flex flex-fill gl-align-items-center page-title-holder"
-  >
-    <h1
-      class="flex-fill gl-font-size-h-display js-dashboard-title page-title text-nowrap"
-    >
-      Environments Dashboard
-    </h1>
-    <gl-button-stub
-      buttontextclasses=""
-      category="primary"
-      class="js-add-projects-button"
-      icon=""
-      role="button"
-      size="medium"
-      tabindex="0"
-      variant="confirm"
-    >
-      Add projects
-    </gl-button-stub>
-  </div>
-  <p
-    class="js-page-limits-message mb-4 mt-2"
-  >
-    This dashboard displays 3 environments per project, and is linked to the Operations Dashboard. When you add or remove a project from one dashboard, GitLab adds or removes the project from the other.
-    <gl-link-stub
-      href="/help/user/operations_dashboard/index.html"
-      target="_blank"
-    >
-      More information
-    </gl-link-stub>
-  </p>
-  <div
-    class="gl-mt-3"
-  >
-    <gl-empty-state-stub
-      contentclass=""
-      invertindarkmode="true"
-      svgheight="150"
-      svgpath="/assets/illustrations/empty-state/empty-radar-md.svg"
-      title="Add a project to the dashboard"
-    >
-      The environments dashboard provides a summary of each project's environments' status, including pipeline and alert statuses.
-      <gl-link-stub
-        class="js-documentation-link"
-        href="/help/user/operations_dashboard/index.html"
-      >
-        View documentation
-      </gl-link-stub>
-      .
-      <gl-button-stub
-        buttontextclasses=""
-        category="primary"
-        class="js-add-projects-button"
-        icon=""
-        role="button"
-        size="medium"
-        tabindex="0"
-        variant="confirm"
-      >
-        Add projects
-      </gl-button-stub>
-    </gl-empty-state-stub>
-  </div>
-</div>
-`;
diff --git a/ee/spec/frontend/environments_dashboard/components/dashboard_spec.js b/ee/spec/frontend/environments_dashboard/components/dashboard_spec.js
index eaa38fa100689..7ffc3b4fc7354 100644
--- a/ee/spec/frontend/environments_dashboard/components/dashboard_spec.js
+++ b/ee/spec/frontend/environments_dashboard/components/dashboard_spec.js
@@ -1,8 +1,8 @@
 import { GlButton, GlEmptyState, GlModal, GlSprintf, GlLink, GlPagination } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
 import Vue, { nextTick } from 'vue';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import component from 'ee/environments_dashboard/components/dashboard/dashboard.vue';
 import Environment from 'ee/environments_dashboard/components/dashboard/environment.vue';
 import ProjectHeader from 'ee/environments_dashboard/components/dashboard/project_header.vue';
@@ -50,7 +50,7 @@ describe('dashboard', () => {
       environmentsDashboardHelpPath: '/help/user/operations_dashboard/index.html',
     };
 
-    wrapper = shallowMount(component, {
+    wrapper = shallowMountExtended(component, {
       propsData,
       store,
       stubs: { GlSprintf },
@@ -62,53 +62,24 @@ describe('dashboard', () => {
   });
 
   const findPagination = () => wrapper.findComponent(GlPagination);
+  const findDashboardTitle = () => wrapper.findByTestId('dashboard-title');
+  const findPageLimitsMessage = () => wrapper.findByTestId('page-limits-message');
 
-  it('should match the snapshot', () => {
-    expect(wrapper.element).toMatchSnapshot();
-  });
-
-  it('renders the dashboard title', () => {
-    expect(wrapper.find('.js-dashboard-title').text()).toBe('Environments Dashboard');
-  });
-
-  it('should render the empty state component', () => {
-    expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
-  });
-
-  it('should not render pagination in empty state', () => {
-    expect(findPagination().exists()).toBe(false);
-  });
-
-  describe('page limits information message', () => {
-    let message;
-
-    beforeEach(() => {
-      message = wrapper.find('.js-page-limits-message');
-    });
-
-    it('renders the message', () => {
-      expect(trimText(message.text())).toBe(
-        'This dashboard displays 3 environments per project, and is linked to the Operations Dashboard. When you add or remove a project from one dashboard, GitLab adds or removes the project from the other. More information',
-      );
+  describe('empty state', () => {
+    it('should render the empty state component', () => {
+      expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
     });
 
-    it('includes the correct documentation link in the message', () => {
-      const helpLink = message.findComponent(GlLink);
-
-      expect(helpLink.text()).toBe('More information');
-      expect(helpLink.attributes('href')).toBe(propsData.environmentsDashboardHelpPath);
+    it('should not the render title', () => {
+      expect(findDashboardTitle().exists()).toBe(false);
     });
-  });
-
-  describe('add projects button', () => {
-    let button;
 
-    beforeEach(() => {
-      button = wrapper.findComponent(GlButton);
+    it('should not the render description', () => {
+      expect(findPageLimitsMessage().exists()).toBe(false);
     });
 
-    it('is labelled correctly', () => {
-      expect(button.text()).toBe('Add projects');
+    it('should not render pagination', () => {
+      expect(findPagination().exists()).toBe(false);
     });
   });
 
@@ -125,6 +96,43 @@ describe('dashboard', () => {
       ];
     });
 
+    it('renders the dashboard title', () => {
+      expect(findDashboardTitle().text()).toBe('Environments Dashboard');
+    });
+
+    describe('page limits information message', () => {
+      let message;
+
+      beforeEach(() => {
+        message = findPageLimitsMessage();
+      });
+
+      it('renders the message', () => {
+        expect(trimText(message.text())).toBe(
+          'This dashboard displays 3 environments per project, and is linked to the Operations Dashboard. When you add or remove a project from one dashboard, GitLab adds or removes the project from the other. More information',
+        );
+      });
+
+      it('includes the correct documentation link in the message', () => {
+        const helpLink = message.findComponent(GlLink);
+
+        expect(helpLink.text()).toBe('More information');
+        expect(helpLink.attributes('href')).toBe(propsData.environmentsDashboardHelpPath);
+      });
+    });
+
+    describe('add projects button', () => {
+      let button;
+
+      beforeEach(() => {
+        button = wrapper.findComponent(GlButton);
+      });
+
+      it('is labelled correctly', () => {
+        expect(button.text()).toBe('Add projects');
+      });
+    });
+
     describe('project header', () => {
       it('should have one project header per project', () => {
         const headers = wrapper.findAllComponents(ProjectHeader);
diff --git a/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js b/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js
index c4721a635d03a..698785370e7a2 100644
--- a/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js
+++ b/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js
@@ -59,18 +59,50 @@ describe('dashboard component', () => {
     mockAxios.restore();
   });
 
-  it('renders dashboard title', () => {
-    const dashboardTitle = wrapper.element.querySelector('.js-dashboard-title');
+  describe('when no projects have been added', () => {
+    beforeEach(() => {
+      store.state.projects = [];
+      store.state.isLoadingProjects = false;
+    });
+
+    it('should render the empty state', () => {
+      expect(findEmptyState().exists()).toBe(true);
+    });
 
-    expect(dashboardTitle.innerText.trim()).toEqual(mockText.DASHBOARD_TITLE);
+    it('should link to the documentation', () => {
+      const link = wrapper.findByTestId('documentation-link');
+
+      expect(link.exists()).toBe(true);
+      expect(link.attributes().href).toEqual(emptyDashboardHelpPath);
+    });
+
+    it('should render the add projects button', () => {
+      const button = findAddProjectButton();
+
+      expect(button.exists()).toBe(true);
+      expect(button.text()).toEqual('Add projects');
+    });
   });
 
-  describe('add projects button', () => {
-    it('renders add projects text', () => {
-      expect(findAddProjectButton().text()).toBe(mockText.ADD_PROJECTS);
+  describe('wrapped components', () => {
+    const projectCount = 3;
+
+    beforeEach(() => {
+      store.state.projects = mockProjectData(projectCount);
+      wrapper = mountComponent();
     });
 
-    describe('when a project is added', () => {
+    describe('dashboard layout', () => {
+      it('renders dashboard title', () => {
+        const dashboardTitle = wrapper.findByTestId('dashboard-title');
+
+        expect(dashboardTitle.text()).toEqual(mockText.DASHBOARD_TITLE);
+      });
+
+      it('renders add projects text', () => {
+        expect(findAddProjectButton().text()).toBe(mockText.ADD_PROJECTS);
+      });
+
       it('immediately requests the project list again', async () => {
         mockAxios.reset();
         mockAxios
@@ -89,17 +121,8 @@ describe('dashboard component', () => {
         expect(findAllProjects()).toHaveLength(2);
       });
     });
-  });
 
-  describe('wrapped components', () => {
     describe('dashboard project component', () => {
-      const projectCount = 1;
-
-      beforeEach(() => {
-        store.state.projects = mockProjectData(projectCount);
-        wrapper = mountComponent();
-      });
-
       it('includes a dashboard project component for each project', () => {
         expect(findAllProjects()).toHaveLength(projectCount);
       });
@@ -189,30 +212,5 @@ describe('dashboard component', () => {
         expect(store.state.selectedProjects).toHaveLength(0);
       });
     });
-
-    describe('when no projects have been added', () => {
-      beforeEach(() => {
-        store.state.projects = [];
-        store.state.isLoadingProjects = false;
-      });
-
-      it('should render the empty state', () => {
-        expect(findEmptyState().exists()).toBe(true);
-      });
-
-      it('should link to the documentation', () => {
-        const link = findEmptyState().find('[data-testid="documentation-link"]');
-
-        expect(link.exists()).toBe(true);
-        expect(link.attributes().href).toEqual(emptyDashboardHelpPath);
-      });
-
-      it('should render the add projects button', () => {
-        const button = findAddProjectButton();
-
-        expect(button.exists()).toBe(true);
-        expect(button.text()).toEqual('Add projects');
-      });
-    });
   });
 });
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a54584ff1b2b0..5d03e45772e69 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -31694,9 +31694,6 @@ msgstr ""
 msgid "Modal updated"
 msgstr ""
 
-msgid "ModalButton|Add projects"
-msgstr ""
-
 msgid "Modal|Close"
 msgstr ""
 
-- 
GitLab