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 212155ea518507dcc2aa7b3e64d579393965d39d..2d468583e80afb27e67b7bacb0d15b5e7e5058ee 100644 --- a/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue +++ b/ee/app/assets/javascripts/environments_dashboard/components/dashboard/dashboard.vue @@ -96,9 +96,6 @@ export default { this.paginateDashboard(newPage); }, }, - showDashboard() { - return this.projects.length || this.isLoadingProjects; - }, projectsPerPage() { return this.projectsPage.pageInfo.perPage; }, @@ -163,7 +160,7 @@ export default { </script> <template> - <div v-if="showDashboard" class="environments-dashboard"> + <div class="environments-dashboard"> <gl-modal :modal-id="$options.modalId" :title="$options.addProjectsModalHeader" @@ -196,17 +193,14 @@ export default { /> </gl-modal> <div class="page-title-holder flex-fill d-flex gl-align-items-center"> - <h1 - class="page-title gl-font-size-h-display text-nowrap flex-fill" - data-testid="dashboard-title" - > + <h1 class="js-dashboard-title page-title gl-font-size-h-display text-nowrap flex-fill"> {{ $options.dashboardHeader }} </h1> - <gl-button v-gl-modal="$options.modalId" data-testid="add-projects-button" variant="confirm"> + <gl-button v-gl-modal="$options.modalId" class="js-add-projects-button" variant="confirm"> {{ $options.addProjectsButton }} </gl-button> </div> - <p class="gl-mt-3 gl-mb-6" data-testid="page-limits-message"> + <p class="mt-2 mb-4 js-page-limits-message"> <gl-sprintf :message="$options.informationText"> <template #link="{ content }"> <gl-link :href="environmentsDashboardHelpPath" target="_blank"> @@ -240,30 +234,26 @@ 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 6032dcc6e846281b3477c1aa1910b2f326e58f72..7baf0fa801c848be9e9315d60d9d2facee1999d2 100644 --- a/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue +++ b/ee/app/assets/javascripts/operations/components/dashboard/dashboard.vue @@ -17,18 +17,9 @@ 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, @@ -83,9 +74,6 @@ export default { this.setProjects(projects); }, }, - showDashboard() { - return this.projects.length || this.isLoadingProjects; - }, isSearchingProjects() { return this.searchCount > 0; }, @@ -94,7 +82,7 @@ export default { }, actionPrimary() { return { - text: this.addProjectsSubmitButton, + text: s__('OperationsDashboard|Add projects'), attributes: { disabled: this.okDisabled, variant: 'confirm', @@ -147,10 +135,10 @@ export default { </script> <template> - <div v-if="showDashboard" class="operations-dashboard"> + <div class="operations-dashboard"> <gl-modal :modal-id="$options.modalId" - :title="$options.addProjectsModalHeader" + :title="s__('OperationsDashboard|Add projects')" :action-primary="actionPrimary" :action-cancel="$options.modal.actionCancel" data-testid="add-projects-modal" @@ -173,11 +161,8 @@ export default { </gl-modal> <div class="page-title-holder flex-fill d-flex gl-align-items-center"> - <h1 - class="page-title gl-font-size-h-display text-nowrap flex-fill" - data-testid="dashboard-title" - > - {{ $options.title }} + <h1 class="js-dashboard-title page-title gl-font-size-h-display text-nowrap flex-fill"> + {{ s__('OperationsDashboard|Operations Dashboard') }} </h1> <gl-button v-if="projects.length" @@ -186,7 +171,7 @@ export default { category="primary" data-testid="add-projects-button" > - {{ $options.addProjectsSubmitButton }} + {{ s__('OperationsDashboard|Add projects') }} </gl-button> </div> <p class="gl-mt-2 gl-mb-4"> @@ -211,30 +196,34 @@ 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 new file mode 100644 index 0000000000000000000000000000000000000000..d68cbec111ca36805d7a556e0b939a82b7b29ea3 --- /dev/null +++ b/ee/spec/frontend/environments_dashboard/components/__snapshots__/dashboard_spec.js.snap @@ -0,0 +1,99 @@ +// 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 7ffc3b4fc735406057a2293c9289b155c1e7d5fa..eaa38fa100689db38707f760537d0ee5f180b912 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 = shallowMountExtended(component, { + wrapper = shallowMount(component, { propsData, store, stubs: { GlSprintf }, @@ -62,24 +62,53 @@ describe('dashboard', () => { }); const findPagination = () => wrapper.findComponent(GlPagination); - const findDashboardTitle = () => wrapper.findByTestId('dashboard-title'); - const findPageLimitsMessage = () => wrapper.findByTestId('page-limits-message'); - describe('empty state', () => { - it('should render the empty state component', () => { - expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true); + 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', + ); }); - it('should not the render title', () => { - expect(findDashboardTitle().exists()).toBe(false); + 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; - it('should not the render description', () => { - expect(findPageLimitsMessage().exists()).toBe(false); + beforeEach(() => { + button = wrapper.findComponent(GlButton); }); - it('should not render pagination', () => { - expect(findPagination().exists()).toBe(false); + it('is labelled correctly', () => { + expect(button.text()).toBe('Add projects'); }); }); @@ -96,43 +125,6 @@ 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 698785370e7a237ec82819be5417ad85f8b9002e..c4721a635d03a634d05d2037c1b07988937d84b7 100644 --- a/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js +++ b/ee/spec/frontend/operations/components/dashboard/dashboard_spec.js @@ -59,50 +59,18 @@ describe('dashboard component', () => { mockAxios.restore(); }); - 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('renders dashboard title', () => { + const dashboardTitle = wrapper.element.querySelector('.js-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'); - }); + expect(dashboardTitle.innerText.trim()).toEqual(mockText.DASHBOARD_TITLE); }); - describe('wrapped components', () => { - const projectCount = 3; - - beforeEach(() => { - store.state.projects = mockProjectData(projectCount); - wrapper = mountComponent(); + describe('add projects button', () => { + it('renders add projects text', () => { + expect(findAddProjectButton().text()).toBe(mockText.ADD_PROJECTS); }); - 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); - }); - + describe('when a project is added', () => { it('immediately requests the project list again', async () => { mockAxios.reset(); mockAxios @@ -121,8 +89,17 @@ 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); }); @@ -212,5 +189,30 @@ 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 ba50eaa0e2d971896ed7732f0398d3c91539af41..6d87e193bde0e0a29b8e512211775df30efc6f01 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -31809,6 +31809,9 @@ msgstr "" msgid "Modal updated" msgstr "" +msgid "ModalButton|Add projects" +msgstr "" + msgid "Modal|Close" msgstr ""