From 618b729140ad1c2fd3597b2852e3402260a082cd Mon Sep 17 00:00:00 2001 From: Brandon Labuschagne <blabuschagne@gitlab.com> Date: Mon, 9 Nov 2020 15:25:16 +0200 Subject: [PATCH 1/3] Add devops adoption table Add a table to the devops adoption feature which displays the data for user defined segments. --- .../components/devops_adoption_app.vue | 89 +++++++-- .../admin/dev_ops_report/constants.js | 18 +- .../devops_adoption_segments.query.graphql | 17 ++ .../components/devops_adoption_app_spec.js | 169 +++++++++++++++++- .../admin/dev_ops_report/mock_data.js | 5 + locale/gitlab.pot | 6 + 6 files changed, 284 insertions(+), 20 deletions(-) create mode 100644 ee/app/assets/javascripts/admin/dev_ops_report/graphql/queries/devops_adoption_segments.query.graphql diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue index 915a7aa8f5251c8e..123389fffc09bd70 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue +++ b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue @@ -1,10 +1,19 @@ <script> -import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import dateformat from 'dateformat'; +import { GlLoadingIcon, GlButton, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui'; import * as Sentry from '~/sentry/wrapper'; import getGroupsQuery from '../graphql/queries/get_groups.query.graphql'; +import devopsAdoptionSegmentsQuery from '../graphql/queries/devops_adoption_segments.query.graphql'; import DevopsAdoptionEmptyState from './devops_adoption_empty_state.vue'; -import { DEVOPS_ADOPTION_STRINGS, MAX_REQUEST_COUNT } from '../constants'; import DevopsAdoptionSegmentModal from './devops_adoption_segment_modal.vue'; +import DevopsAdoptionTable from './devops_adoption_table.vue'; +import { + DEVOPS_ADOPTION_STRINGS, + DEVOPS_ADOPTION_ERROR_KEYS, + MAX_REQUEST_COUNT, + DATE_TIME_FORMAT, + DEVOPS_ADOPTION_SEGMENT_MODAL_ID, +} from '../constants'; export default { name: 'DevopsAdoptionApp', @@ -13,37 +22,71 @@ export default { GlLoadingIcon, DevopsAdoptionEmptyState, DevopsAdoptionSegmentModal, + DevopsAdoptionTable, + GlButton, + GlSprintf, + }, + directives: { + GlModal: GlModalDirective, }, i18n: { ...DEVOPS_ADOPTION_STRINGS.app, }, + devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID, data() { return { + isLoadingGroups: false, requestCount: 0, - loadingError: false, - isLoading: false, selectedSegmentId: null, + errors: { + [DEVOPS_ADOPTION_ERROR_KEYS.groups]: false, + [DEVOPS_ADOPTION_ERROR_KEYS.segments]: false, + }, groups: { nodes: [], pageInfo: null, }, }; }, + apollo: { + devopsAdoptionSegments: { + query: devopsAdoptionSegmentsQuery, + error(error) { + this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.segments, error); + this.devopsAdoptionSegments = null; + }, + }, + }, computed: { hasGroupData() { return Boolean(this.groups?.nodes?.length); }, + hasSegmentsData() { + return Boolean(this.devopsAdoptionSegments?.nodes?.length); + }, + hasLoadingError() { + return Object.values(this.errors).some(error => error === true); + }, + timestamp() { + return dateformat( + this.devopsAdoptionSegments?.nodes[0]?.latestSnapshot?.recordedAt, + DATE_TIME_FORMAT, + ); + }, + isLoading() { + return this.isLoadingGroups || this.$apollo.queries.devopsAdoptionSegments.loading; + }, }, created() { this.fetchGroups(); }, methods: { - handleError(error) { - this.loadingError = true; + handleError(key, error) { + this.errors[key] = true; Sentry.captureException(error); }, fetchGroups(nextPage) { - this.isLoading = true; + this.isLoadingGroups = true; this.$apollo .query({ query: getGroupsQuery, @@ -64,25 +107,45 @@ export default { if (this.requestCount < MAX_REQUEST_COUNT && pageInfo?.nextPage) { this.fetchGroups(pageInfo.nextPage); } else { - this.isLoading = false; + this.isLoadingGroups = false; } }) - .catch(this.handleError); + .catch(error => this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.groups, error)); }, }, }; </script> <template> - <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-3"> - {{ $options.i18n.groupsError }} - </gl-alert> + <div v-if="hasLoadingError"> + <div v-for="(error, key) in errors" :key="key"> + <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mt-3"> + {{ $options.i18n[key] }} + </gl-alert> + </div> + </div> <gl-loading-icon v-else-if="isLoading" size="md" class="gl-my-5" /> <div v-else> - <devops-adoption-empty-state :has-groups-data="hasGroupData" /> <devops-adoption-segment-modal v-if="hasGroupData" :groups="groups.nodes" :segment-id="selectedSegmentId" /> + <div v-if="hasSegmentsData" class="gl-mt-3"> + <div + class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-my-3" + data-testid="tableHeader" + > + <span class="gl-text-gray-400"> + <gl-sprintf :message="$options.i18n.tableHeader.text"> + <template #timestamp>{{ timestamp }}</template> + </gl-sprintf> + </span> + <gl-button v-gl-modal="$options.devopsSegmentModalId">{{ + $options.i18n.tableHeader.button + }}</gl-button> + </div> + <devops-adoption-table :segments="devopsAdoptionSegments.nodes" /> + </div> + <devops-adoption-empty-state v-else :has-groups-data="hasGroupData" /> </div> </template> diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/constants.js b/ee/app/assets/javascripts/admin/dev_ops_report/constants.js index 7f47c0c593434ef6..404f143cd565d6f2 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/constants.js +++ b/ee/app/assets/javascripts/admin/dev_ops_report/constants.js @@ -4,9 +4,25 @@ export const MAX_REQUEST_COUNT = 10; export const DEVOPS_ADOPTION_SEGMENT_MODAL_ID = 'devopsSegmentModal'; +export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM'; + +export const DEVOPS_ADOPTION_ERROR_KEYS = { + groups: 'groupsError', + segments: 'segmentsError', +}; + export const DEVOPS_ADOPTION_STRINGS = { app: { - groupsError: s__('DevopsAdoption|There was an error fetching Groups'), + [DEVOPS_ADOPTION_ERROR_KEYS.groups]: s__('DevopsAdoption|There was an error fetching Groups'), + [DEVOPS_ADOPTION_ERROR_KEYS.segments]: s__( + 'DevopsAdoption|There was an error fetching Segments', + ), + tableHeader: { + text: s__( + 'DevopsAdoption|Feature adoption is based on usage over the last 30 days. Last updated: %{timestamp}.', + ), + button: s__('DevopsAdoption|Add new segment'), + }, }, emptyState: { title: s__('DevopsAdoption|Add a segment to get started'), diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/graphql/queries/devops_adoption_segments.query.graphql b/ee/app/assets/javascripts/admin/dev_ops_report/graphql/queries/devops_adoption_segments.query.graphql new file mode 100644 index 0000000000000000..52898e142d0c3d89 --- /dev/null +++ b/ee/app/assets/javascripts/admin/dev_ops_report/graphql/queries/devops_adoption_segments.query.graphql @@ -0,0 +1,17 @@ +query devopsAdoptionSegments { + devopsAdoptionSegments { + nodes { + name + latestSnapshot { + issueOpened + mergeRequestOpened + mergeRequestApproved + runnerConfigured + pipelineSucceeded + deploySucceeded + securityScanSucceeded + recordedAt + } + } + } +} diff --git a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js index 5aea5cec6f72b966..2b6958275913d0e1 100644 --- a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js +++ b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js @@ -1,16 +1,28 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import { GlAlert, GlButton, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { getByText } from '@testing-library/dom'; import createMockApollo from 'jest/helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import getGroupsQuery from 'ee/admin/dev_ops_report/graphql/queries/get_groups.query.graphql'; +import devopsAdoptionSegments from 'ee/admin/dev_ops_report/graphql/queries/devops_adoption_segments.query.graphql'; import DevopsAdoptionApp from 'ee/admin/dev_ops_report/components/devops_adoption_app.vue'; import DevopsAdoptionEmptyState from 'ee/admin/dev_ops_report/components/devops_adoption_empty_state.vue'; +import DevopsAdoptionTable from 'ee/admin/dev_ops_report/components/devops_adoption_table.vue'; import DevopsAdoptionSegmentModal from 'ee/admin/dev_ops_report/components/devops_adoption_segment_modal.vue'; -import { DEVOPS_ADOPTION_STRINGS } from 'ee/admin/dev_ops_report/constants'; +import { + DEVOPS_ADOPTION_STRINGS, + DEVOPS_ADOPTION_SEGMENT_MODAL_ID, +} from 'ee/admin/dev_ops_report/constants'; import * as Sentry from '~/sentry/wrapper'; -import { groupNodes, nextGroupNode, groupPageInfo } from '../mock_data'; +import { + groupNodes, + nextGroupNode, + groupPageInfo, + devopsAdoptionSegmentsData, + devopsAdoptionSegmentsDataEmpty, +} from '../mock_data'; const localVue = createLocalVue(); Vue.use(VueApollo); @@ -24,9 +36,15 @@ const initialResponse = { describe('DevopsAdoptionApp', () => { let wrapper; + const groupsEmpty = jest.fn().mockResolvedValueOnce({ __typename: 'Groups', nodes: [] }); + const segmentsEmpty = jest + .fn() + .mockResolvedValue({ data: { devopsAdoptionSegments: devopsAdoptionSegmentsDataEmpty } }); + function createMockApolloProvider(options = {}) { - const { groupsSpy } = options; - const mockApollo = createMockApollo([], { + const { groupsSpy = groupsEmpty, segmentsSpy = segmentsEmpty } = options; + + const mockApollo = createMockApollo([[devopsAdoptionSegments, segmentsSpy]], { Query: { groups: groupsSpy, }, @@ -47,6 +65,9 @@ describe('DevopsAdoptionApp', () => { return shallowMount(DevopsAdoptionApp, { localVue, apolloProvider: mockApollo, + stubs: { + GlSprintf, + }, data() { return data; }, @@ -163,7 +184,11 @@ describe('DevopsAdoptionApp', () => { .fn() .mockResolvedValueOnce(initialResponse) // `fetchMore` response - .mockResolvedValueOnce({ __typename: 'Groups', nodes: [nextGroupNode], nextPage: null }); + .mockResolvedValueOnce({ + __typename: 'Groups', + nodes: [nextGroupNode], + nextPage: null, + }); const mockApollo = createMockApolloProvider({ groupsSpy }); wrapper = createComponent({ mockApollo }); await waitForPromises(); @@ -253,4 +278,136 @@ describe('DevopsAdoptionApp', () => { }); }); }); + + describe('segments data', () => { + describe('when loading', () => { + beforeEach(async () => { + const segmentsLoading = jest.fn().mockResolvedValue(new Promise(() => {})); + const mockApollo = createMockApolloProvider({ segmentsSpy: segmentsLoading }); + wrapper = createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('does not display the empty state', () => { + expect(wrapper.find(DevopsAdoptionEmptyState).exists()).toBe(false); + }); + + it('displays the loader', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + }); + + describe('when there is no segment data', () => { + beforeEach(async () => { + const mockApollo = createMockApolloProvider(); + wrapper = createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('displays the empty state', () => { + expect(wrapper.find(DevopsAdoptionEmptyState).exists()).toBe(true); + }); + + it('does not display the table', () => { + expect(wrapper.find(DevopsAdoptionTable).exists()).toBe(false); + }); + }); + + describe('when there is segment data', () => { + beforeEach(async () => { + const segmentsWithData = jest + .fn() + .mockResolvedValue({ data: { devopsAdoptionSegments: devopsAdoptionSegmentsData } }); + const mockApollo = createMockApolloProvider({ segmentsSpy: segmentsWithData }); + wrapper = createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('does not display the empty state', () => { + expect(wrapper.find(DevopsAdoptionEmptyState).exists()).toBe(false); + }); + + it('displays the table', () => { + expect(wrapper.find(DevopsAdoptionTable).exists()).toBe(true); + }); + + describe('table header', () => { + let tableHeader; + + beforeEach(() => { + tableHeader = wrapper.find("[data-testid='tableHeader']"); + }); + + afterEach(() => { + tableHeader = null; + }); + + it('displays the table header', () => { + expect(tableHeader.exists()).toBe(true); + }); + + it('displays the header text', () => { + const text = + 'Feature adoption is based on usage over the last 30 days. Last updated: 2020-10-31 23:59.'; + expect(getByText(wrapper.element, text)).not.toBeNull(); + }); + + describe('segment modal button', () => { + let segmentButton; + + beforeEach(() => { + segmentButton = tableHeader.find(GlButton); + }); + + afterEach(() => { + segmentButton = null; + }); + + it('displays the add segment button', () => { + expect(segmentButton.exists()).toBe(true); + }); + + it('calls the gl-modal show', async () => { + const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit'); + + segmentButton.trigger('click'); + + expect(rootEmit.mock.calls[0][0]).toContain('show'); + expect(rootEmit.mock.calls[0][1]).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID); + }); + }); + }); + }); + + describe('when there is an error', () => { + const segmentsErrorMessage = 'Error: bar!'; + + beforeEach(async () => { + jest.spyOn(Sentry, 'captureException'); + const segmentsError = jest.fn().mockRejectedValue(segmentsErrorMessage); + const mockApollo = createMockApolloProvider({ segmentsSpy: segmentsError }); + wrapper = createComponent({ mockApollo }); + await waitForPromises(); + }); + + it('does not display the loader', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + }); + + it('does not render the segment modal', () => { + expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false); + }); + + it('does not render the table', () => { + expect(wrapper.find(DevopsAdoptionTable).exists()).toBe(false); + }); + + it('displays the error message and calls Sentry', () => { + const alert = wrapper.find(GlAlert); + expect(alert.exists()).toBe(true); + expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.segmentsError); + expect(Sentry.captureException.mock.calls[0][0].networkError).toBe(segmentsErrorMessage); + }); + }); + }); }); diff --git a/ee/spec/frontend/admin/dev_ops_report/mock_data.js b/ee/spec/frontend/admin/dev_ops_report/mock_data.js index d4baa6bb39d29a1e..2ad9f2d3c98385f2 100644 --- a/ee/spec/frontend/admin/dev_ops_report/mock_data.js +++ b/ee/spec/frontend/admin/dev_ops_report/mock_data.js @@ -48,6 +48,11 @@ export const devopsAdoptionSegmentsData = { __typename: 'devopsAdoptionSegments', }; +export const devopsAdoptionSegmentsDataEmpty = { + nodes: [], + __typename: 'devopsAdoptionSegments', +}; + export const devopsAdoptionTableHeaders = [ 'Segment', 'Issues', diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d27d6f6604d386ad..138310175a9a2a8c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9552,6 +9552,9 @@ msgstr "" msgid "DevopsAdoption|DevOps adoption uses segments to track adoption across key features. Segments are a way to track multiple related projects and groups at once. For example, you could create a segment for the engineering department or a particular product team." msgstr "" +msgid "DevopsAdoption|Feature adoption is based on usage over the last 30 days. Last updated: %{timestamp}." +msgstr "" + msgid "DevopsAdoption|Issues" msgstr "" @@ -9582,6 +9585,9 @@ msgstr "" msgid "DevopsAdoption|There was an error fetching Groups" msgstr "" +msgid "DevopsAdoption|There was an error fetching Segments" +msgstr "" + msgid "DevopsReport|Adoption" msgstr "" -- GitLab From 716a18e5b3b2dd93fb17115cc935b400a37437f2 Mon Sep 17 00:00:00 2001 From: Brandon Labuschagne <blabuschagne@gitlab.com> Date: Tue, 24 Nov 2020 09:53:19 +0200 Subject: [PATCH 2/3] Apply reviewer feedback In addition to the reviewer feedback I have updated the error messages to include a remedy action. --- .../admin/dev_ops_report/components/devops_adoption_app.vue | 1 - ee/app/assets/javascripts/admin/dev_ops_report/constants.js | 6 ++++-- .../dev_ops_report/components/devops_adoption_app_spec.js | 5 ++++- locale/gitlab.pot | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue index 123389fffc09bd70..2076107980df29b5 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue +++ b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue @@ -53,7 +53,6 @@ export default { query: devopsAdoptionSegmentsQuery, error(error) { this.handleError(DEVOPS_ADOPTION_ERROR_KEYS.segments, error); - this.devopsAdoptionSegments = null; }, }, }, diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/constants.js b/ee/app/assets/javascripts/admin/dev_ops_report/constants.js index 404f143cd565d6f2..22e8143bd0a787ee 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/constants.js +++ b/ee/app/assets/javascripts/admin/dev_ops_report/constants.js @@ -13,9 +13,11 @@ export const DEVOPS_ADOPTION_ERROR_KEYS = { export const DEVOPS_ADOPTION_STRINGS = { app: { - [DEVOPS_ADOPTION_ERROR_KEYS.groups]: s__('DevopsAdoption|There was an error fetching Groups'), + [DEVOPS_ADOPTION_ERROR_KEYS.groups]: s__( + 'DevopsAdoption|There was an error fetching Groups. Please refresh the page to try again.', + ), [DEVOPS_ADOPTION_ERROR_KEYS.segments]: s__( - 'DevopsAdoption|There was an error fetching Segments', + 'DevopsAdoption|There was an error fetching Segments. Please refresh the page to try again.', ), tableHeader: { text: s__( diff --git a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js index 2b6958275913d0e1..26bae07178b0dbad 100644 --- a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js +++ b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js @@ -402,10 +402,13 @@ describe('DevopsAdoptionApp', () => { expect(wrapper.find(DevopsAdoptionTable).exists()).toBe(false); }); - it('displays the error message and calls Sentry', () => { + it('displays the error message ', () => { const alert = wrapper.find(GlAlert); expect(alert.exists()).toBe(true); expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.segmentsError); + }); + + it('calls Sentry', () => { expect(Sentry.captureException.mock.calls[0][0].networkError).toBe(segmentsErrorMessage); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 138310175a9a2a8c..2896a88b7caa4e73 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9582,10 +9582,10 @@ msgstr "" msgid "DevopsAdoption|Segment" msgstr "" -msgid "DevopsAdoption|There was an error fetching Groups" +msgid "DevopsAdoption|There was an error fetching Groups. Please refresh the page to try again." msgstr "" -msgid "DevopsAdoption|There was an error fetching Segments" +msgid "DevopsAdoption|There was an error fetching Segments. Please refresh the page to try again." msgstr "" msgid "DevopsReport|Adoption" -- GitLab From 3f3c5ed1f3f6c545786beceafb6eac8c69a151c5 Mon Sep 17 00:00:00 2001 From: Brandon Labuschagne <blabuschagne@gitlab.com> Date: Wed, 25 Nov 2020 09:33:19 +0200 Subject: [PATCH 3/3] Apply maintainer suggestion --- .../dev_ops_report/components/devops_adoption_app.vue | 6 +++--- .../dev_ops_report/components/devops_adoption_table.vue | 7 +++++++ .../dev_ops_report/components/devops_adoption_app_spec.js | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue index 2076107980df29b5..96c93c6aabc52ae2 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue +++ b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_app.vue @@ -116,11 +116,11 @@ export default { </script> <template> <div v-if="hasLoadingError"> - <div v-for="(error, key) in errors" :key="key"> - <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mt-3"> + <template v-for="(error, key) in errors"> + <gl-alert v-if="error" :key="key" variant="danger" :dismissible="false" class="gl-mt-3"> {{ $options.i18n[key] }} </gl-alert> - </div> + </template> </div> <gl-loading-icon v-else-if="isLoading" size="md" class="gl-my-5" /> <div v-else> diff --git a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_table.vue b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_table.vue index 1da63f8a07069f8e..cc99fcd736611430 100644 --- a/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_table.vue +++ b/ee/app/assets/javascripts/admin/dev_ops_report/components/devops_adoption_table.vue @@ -84,6 +84,7 @@ export default { <template #cell(issueOpened)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.ISSUES" :enabled="item.latestSnapshot.issueOpened" /> @@ -91,6 +92,7 @@ export default { <template #cell(mergeRequestOpened)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.MRS" :enabled="item.latestSnapshot.mergeRequestOpened" /> @@ -98,6 +100,7 @@ export default { <template #cell(mergeRequestApproved)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.APPROVALS" :enabled="item.latestSnapshot.mergeRequestApproved" /> @@ -105,6 +108,7 @@ export default { <template #cell(runnerConfigured)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.RUNNERS" :enabled="item.latestSnapshot.runnerConfigured" /> @@ -112,6 +116,7 @@ export default { <template #cell(pipelineSucceeded)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.PIPELINES" :enabled="item.latestSnapshot.pipelineSucceeded" /> @@ -119,6 +124,7 @@ export default { <template #cell(deploySucceeded)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.DEPLOYS" :enabled="item.latestSnapshot.deploySucceeded" /> @@ -126,6 +132,7 @@ export default { <template #cell(securityScanSucceeded)="{ item }"> <devops-adoption-table-cell-flag + v-if="item.latestSnapshot" :data-testid="$options.testids.SCANNING" :enabled="item.latestSnapshot.securityScanSucceeded" /> diff --git a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js index 26bae07178b0dbad..8275e8ae3a7263ac 100644 --- a/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js +++ b/ee/spec/frontend/admin/dev_ops_report/components/devops_adoption_app_spec.js @@ -36,7 +36,7 @@ const initialResponse = { describe('DevopsAdoptionApp', () => { let wrapper; - const groupsEmpty = jest.fn().mockResolvedValueOnce({ __typename: 'Groups', nodes: [] }); + const groupsEmpty = jest.fn().mockResolvedValue({ __typename: 'Groups', nodes: [] }); const segmentsEmpty = jest .fn() .mockResolvedValue({ data: { devopsAdoptionSegments: devopsAdoptionSegmentsDataEmpty } }); -- GitLab