diff --git a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js deleted file mode 100644 index ff8b4c56321a3362fc00224b01800f62466f9a1f..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js +++ /dev/null @@ -1 +0,0 @@ -export default {}; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index f609ca5f22d9330cb43f718eb0a66f6fd5e8d281..4b35b7d360c9a35f8c24a55f3669f0717649077c 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -2,7 +2,6 @@ import $ from 'jquery'; import Vue from 'vue'; import Cookies from 'js-cookie'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; -import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins'; import Flash from '../flash'; import { __ } from '~/locale'; import Translate from '../vue_shared/translate'; @@ -45,7 +44,6 @@ export default () => { import('ee_component/analytics/shared/components/date_range_dropdown.vue'), 'stage-nav-item': stageNavItem, }, - mixins: [filterMixins], data() { return { store: CycleAnalyticsStore, diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue index 4e862c5294645c6796fc091524eaa22500f71cdd..92fe1dadb8891f6abcb6dd7a0a54a9f057c7e2bd 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue @@ -2,7 +2,7 @@ import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { mapActions, mapState, mapGetters } from 'vuex'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; -import { PROJECTS_PER_PAGE, STAGE_ACTIONS } from '../constants'; +import { PROJECTS_PER_PAGE } from '../constants'; import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter.vue'; import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue'; import { SIMILARITY_ORDER, LAST_ACTIVITY_AT, DATE_RANGE_LIMIT } from '../../shared/constants'; @@ -12,14 +12,12 @@ import DurationChart from './duration_chart.vue'; import TypeOfWorkCharts from './type_of_work_charts.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue'; import { toYmd } from '../../shared/utils'; -import RecentActivityCard from './recent_activity_card.vue'; -import TimeMetricsCard from './time_metrics_card.vue'; import StageTableNav from './stage_table_nav.vue'; import CustomStageForm from './custom_stage_form.vue'; import PathNavigation from './path_navigation.vue'; -import MetricCard from '../../shared/components/metric_card.vue'; import FilterBar from './filter_bar.vue'; import ValueStreamSelect from './value_stream_select.vue'; +import Metrics from './metrics.vue'; export default { name: 'CycleAnalytics', @@ -32,15 +30,13 @@ export default { ProjectsDropdownFilter, StageTable, TypeOfWorkCharts, - RecentActivityCard, - TimeMetricsCard, CustomStageForm, StageTableNav, PathNavigation, - MetricCard, FilterBar, ValueStreamSelect, UrlSync, + Metrics, }, props: { emptyStateSvgPath: { @@ -215,7 +211,6 @@ export default { min_access_level: featureAccessLevel.EVERYONE, }, maxDateRange: DATE_RANGE_LIMIT, - STAGE_ACTIONS, }; </script> <template> @@ -304,23 +299,7 @@ export default { " /> <div v-else-if="!errorCode"> - <div class="js-recent-activity gl-mt-3 gl-display-flex"> - <div class="gl-flex-fill-1 gl-pr-2"> - <time-metrics-card - #default="{ metrics, loading }" - :group-path="currentGroupPath" - :additional-params="cycleAnalyticsRequestParams" - > - <metric-card :title="__('Time')" :metrics="metrics" :is-loading="loading" /> - </time-metrics-card> - </div> - <div class="gl-flex-fill-1 gl-pl-2"> - <recent-activity-card - :group-path="currentGroupPath" - :additional-params="cycleAnalyticsRequestParams" - /> - </div> - </div> + <metrics :group-path="currentGroupPath" :request-params="cycleAnalyticsRequestParams" /> <div v-if="isLoading"> <gl-loading-icon class="mt-4" size="md" /> </div> @@ -356,7 +335,7 @@ export default { :events="formEvents" @createStage="onCreateCustomStage" @updateStage="onUpdateCustomStage" - @clearErrors="$emit('clearFormErrors')" + @clearErrors="$emit('clear-form-errors')" /> </template> </stage-table> diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/metrics.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/metrics.vue new file mode 100644 index 0000000000000000000000000000000000000000..3cc3b5f03d99c30d63dd703708ec362210d99a17 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/metrics.vue @@ -0,0 +1,48 @@ +<script> +import { OVERVIEW_METRICS } from '../constants'; +import TimeMetricsCard from './time_metrics_card.vue'; +import MetricCard from '../../shared/components/metric_card.vue'; + +export default { + name: 'OverviewActivity', + components: { + TimeMetricsCard, + MetricCard, + }, + props: { + groupPath: { + type: String, + required: true, + }, + requestParams: { + type: Object, + required: true, + }, + }, + overviewMetrics: OVERVIEW_METRICS, +}; +</script> +<template> + <div class="js-recent-activity gl-mt-3 gl-display-flex"> + <div class="gl-flex-fill-1 gl-pr-2"> + <time-metrics-card + #default="{ metrics, loading }" + :group-path="groupPath" + :additional-params="requestParams" + :request-type="$options.overviewMetrics.TIME_SUMMARY" + > + <metric-card :title="__('Time')" :metrics="metrics" :is-loading="loading" /> + </time-metrics-card> + </div> + <div class="gl-flex-fill-1 gl-pl-2"> + <time-metrics-card + #default="{ metrics, loading }" + :group-path="groupPath" + :additional-params="requestParams" + :request-type="$options.overviewMetrics.RECENT_ACTIVITY" + > + <metric-card :title="__('Recent Activity')" :metrics="metrics" :is-loading="loading" /> + </time-metrics-card> + </div> + </div> +</template> diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/recent_activity_card.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/recent_activity_card.vue deleted file mode 100644 index 95564456f7508f41af56553a3b425148c570d9f2..0000000000000000000000000000000000000000 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/recent_activity_card.vue +++ /dev/null @@ -1,63 +0,0 @@ -<script> -import Api from 'ee/api'; -import { __ } from '~/locale'; -import createFlash from '~/flash'; -import MetricCard from '../../shared/components/metric_card.vue'; -import { removeFlash, prepareTimeMetricsData } from '../utils'; - -export default { - name: 'RecentActivityCard', - components: { - MetricCard, - }, - props: { - groupPath: { - type: String, - required: true, - }, - additionalParams: { - type: Object, - required: false, - default: null, - }, - }, - data() { - return { - data: [], - loading: false, - }; - }, - watch: { - additionalParams() { - this.fetchData(); - }, - }, - mounted() { - this.fetchData(); - }, - methods: { - fetchData() { - removeFlash(); - this.loading = true; - return Api.cycleAnalyticsSummaryData( - this.groupPath, - this.additionalParams ? this.additionalParams : {}, - ) - .then(({ data }) => { - this.data = prepareTimeMetricsData(data); - }) - .catch(() => { - createFlash( - __('There was an error while fetching value stream analytics recent activity data.'), - ); - }) - .finally(() => { - this.loading = false; - }); - }, - }, -}; -</script> -<template> - <metric-card :title="__('Recent Activity')" :metrics="data" :is-loading="loading" /> -</template> diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/time_metrics_card.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/time_metrics_card.vue index 34895b91baf65cf57ffd02028f9bba6f64c0e28c..f6df5bbd0fedabac9b2f0ad9859191cab2eb55e6 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/time_metrics_card.vue +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/time_metrics_card.vue @@ -1,15 +1,22 @@ <script> import Api from 'ee/api'; -import { __, s__ } from '~/locale'; +import { sprintf, __, s__ } from '~/locale'; import createFlash from '~/flash'; import MetricCard from '../../shared/components/metric_card.vue'; import { removeFlash, prepareTimeMetricsData } from '../utils'; +import { OVERVIEW_METRICS } from '../constants'; const I18N_TEXT = { 'lead-time': s__('ValueStreamAnalytics|Median time from issue created to issue closed.'), 'cycle-time': s__('ValueStreamAnalytics|Median time from first commit to issue closed.'), }; +const requestData = ({ requestType, groupPath, additionalParams }) => { + return requestType === OVERVIEW_METRICS.TIME_SUMMARY + ? Api.cycleAnalyticsTimeSummaryData(groupPath, additionalParams) + : Api.cycleAnalyticsSummaryData(groupPath, additionalParams); +}; + export default { name: 'TimeMetricsCard', components: { @@ -25,6 +32,11 @@ export default { required: false, default: () => ({}), }, + requestType: { + type: String, + required: true, + validator: t => OVERVIEW_METRICS[t], + }, }, data() { return { @@ -44,13 +56,22 @@ export default { fetchData() { removeFlash(); this.loading = true; - return Api.cycleAnalyticsTimeSummaryData(this.groupPath, this.additionalParams) + return requestData(this) .then(({ data }) => { this.data = prepareTimeMetricsData(data, I18N_TEXT); }) .catch(() => { + const requestTypeName = + this.requestType === OVERVIEW_METRICS.TIME_SUMMARY + ? __('time summary') + : __('recent activity'); createFlash( - __('There was an error while fetching value stream analytics time summary data.'), + sprintf( + s__( + 'There was an error while fetching value stream analytics %{requestTypeName} data.', + ), + { requestTypeName }, + ), ); }) .finally(() => { diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/constants.js b/ee/app/assets/javascripts/analytics/cycle_analytics/constants.js index e969462917ac72413f50f53f2f9596d86443a098..eb17d5ae14882923f7201bb0b439cc6466d85776 100644 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/constants.js +++ b/ee/app/assets/javascripts/analytics/cycle_analytics/constants.js @@ -78,3 +78,8 @@ export const CAPITALIZED_STAGE_NAME = Object.keys(STAGE_NAME).reduce((acc, stage export const PATH_HOME_ICON = 'home'; export const DEFAULT_VALUE_STREAM_ID = 'default'; + +export const OVERVIEW_METRICS = { + TIME_SUMMARY: 'TIME_SUMMARY', + RECENT_ACTIVITY: 'RECENT_ACTIVITY', +}; diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js b/ee/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js deleted file mode 100644 index 0d509d449220024cb43587e86981b8edfcff1eec..0000000000000000000000000000000000000000 --- a/ee/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js +++ /dev/null @@ -1,36 +0,0 @@ -export default { - data() { - return { - dateOptions: [7, 30, 90], - selectedGroup: null, - selectedProjectIds: [], - multiProjectSelect: true, - }; - }, - methods: { - renderSelectedGroup(selectedItemURL) { - this.service = this.createCycleAnalyticsService(selectedItemURL); - this.loadAnalyticsData(); - }, - setSelectedGroup(selectedGroup) { - this.selectedGroup = selectedGroup; - this.renderSelectedGroup(`/groups/${selectedGroup.path}/-/value_stream_analytics`); - }, - setSelectedProjects(selectedProjects) { - this.selectedProjectIds = selectedProjects.map(value => value.id); - this.loadAnalyticsData(); - }, - setSelectedDate(days) { - if (this.startDate !== days) { - this.startDate = days; - this.loadAnalyticsData(); - } - }, - loadAnalyticsData() { - this.fetchCycleAnalyticsData({ - startDate: this.startDate, - projectIds: this.selectedProjectIds, - }); - }, - }, -}; diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/metrics_spec.js.snap b/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/metrics_spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..7a38186bb0845066db5ccdcde15fe68dfb2f0d38 --- /dev/null +++ b/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/metrics_spec.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Metrics renders the recent activity 1`] = `"<time-metrics-card-stub grouppath=\\"foo\\" additionalparams=\\"[object Object]\\" requesttype=\\"RECENT_ACTIVITY\\"></time-metrics-card-stub>"`; + +exports[`Metrics renders the time summary 1`] = `"<time-metrics-card-stub grouppath=\\"foo\\" additionalparams=\\"[object Object]\\" requesttype=\\"TIME_SUMMARY\\"></time-metrics-card-stub>"`; diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/recent_activity_card_spec.js.snap b/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/recent_activity_card_spec.js.snap deleted file mode 100644 index 739151a6d40476e17e0bb7e225f927ac2ef16cf7..0000000000000000000000000000000000000000 --- a/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/recent_activity_card_spec.js.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`RecentActivityCard matches the snapshot 1`] = ` -<div - class="card" -> - <!----> - <div - class="card-header" - > - <strong> - Recent Activity - </strong> - </div> - <div - class="card-body" - > - <!----> - <!----> - - <div - class="gl-display-flex" - > - <div - class="js-metric-card-item gl-flex-grow-1 gl-text-center" - > - <h3 - class="gl-my-2" - > - 4 - </h3> - - <p - class="text-secondary gl-font-sm gl-mb-2" - > - - New Issues - - <!----> - </p> - </div> - <div - class="js-metric-card-item gl-flex-grow-1 gl-text-center" - > - <h3 - class="gl-my-2" - > - - - </h3> - - <p - class="text-secondary gl-font-sm gl-mb-2" - > - - Deploys - - <!----> - </p> - </div> - <div - class="js-metric-card-item gl-flex-grow-1 gl-text-center" - > - <h3 - class="gl-my-2" - > - - - </h3> - - <p - class="text-secondary gl-font-sm gl-mb-2" - > - - Deployment Frequency - - <!----> - </p> - </div> - </div> - - </div> - <!----> - <!----> -</div> -`; diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/time_metrics_card_spec.js.snap b/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/time_metrics_card_spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..a6c0834cf193584cefd9f5ccb382df3e032e8271 --- /dev/null +++ b/ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/time_metrics_card_spec.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TimeMetricsCard Recent activity renders the Recent activity metric 1`] = `"<div><span>4 </span><span>- </span><span>- per day</span></div>"`; + +exports[`TimeMetricsCard Time summary renders the Time summary metric 1`] = `"<div><span>4.5 days</span><span>3.0 days</span></div>"`; diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js index 6390e775d57a50a3c8f312316a8ce5b21ee0ac78..4d4c7afceead3b337a0a5943475354fe80a965be 100644 --- a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js +++ b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js @@ -7,8 +7,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import GroupsDropdownFilter from 'ee/analytics/shared/components/groups_dropdown_filter.vue'; import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue'; -import RecentActivityCard from 'ee/analytics/cycle_analytics/components/recent_activity_card.vue'; -import TimeMetricsCard from 'ee/analytics/cycle_analytics/components/time_metrics_card.vue'; +import Metrics from 'ee/analytics/cycle_analytics/components/metrics.vue'; import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigation.vue'; import StageTable from 'ee/analytics/cycle_analytics/components/stage_table.vue'; import StageTableNav from 'ee/analytics/cycle_analytics/components/stage_table_nav.vue'; @@ -38,7 +37,6 @@ const localVue = createLocalVue(); localVue.use(Vuex); const defaultStubs = { - 'recent-activity-card': true, 'stage-event-list': true, 'stage-nav-item': true, 'tasks-by-type-chart': true, @@ -46,6 +44,7 @@ const defaultStubs = { DurationChart: true, GroupsDropdownFilter: true, ValueStreamSelect: true, + Metrics: true, }; const defaultFeatureFlags = { @@ -150,12 +149,8 @@ describe('Cycle Analytics component', () => { expect(wrapper.find(Daterange).exists()).toBe(flag); }; - const displaysRecentActivityCard = flag => { - expect(wrapper.find(RecentActivityCard).exists()).toBe(flag); - }; - - const displaysTimeMetricsCard = flag => { - expect(wrapper.find(TimeMetricsCard).exists()).toBe(flag); + const displaysMetrics = flag => { + expect(wrapper.contains(Metrics)).toBe(flag); }; const displaysStageTable = flag => { @@ -225,12 +220,8 @@ describe('Cycle Analytics component', () => { displaysDateRangePicker(false); }); - it('does not display the recent activity card', () => { - displaysRecentActivityCard(false); - }); - - it('does not display the time metrics card', () => { - displaysTimeMetricsCard(false); + it('does not display the metrics cards', () => { + displaysMetrics(false); }); it('does not display the stage table', () => { @@ -335,12 +326,8 @@ describe('Cycle Analytics component', () => { displaysDateRangePicker(true); }); - it('displays the recent activity card', () => { - displaysRecentActivityCard(true); - }); - - it('displays the time metrics card', () => { - displaysTimeMetricsCard(true); + it('displays the metrics', () => { + displaysMetrics(true); }); it('displays the stage table', () => { @@ -473,12 +460,8 @@ describe('Cycle Analytics component', () => { displaysDateRangePicker(false); }); - it('does not display the recent activity card', () => { - displaysRecentActivityCard(false); - }); - - it('does not display the time metrics card', () => { - displaysTimeMetricsCard(false); + it('does not display the metrics', () => { + displaysMetrics(false); }); it('does not display the stage table', () => { diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/metrics_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/metrics_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..4b58a849f4a1521b3a1e7bb64c4894fb35377d76 --- /dev/null +++ b/ee/spec/frontend/analytics/cycle_analytics/components/metrics_spec.js @@ -0,0 +1,39 @@ +import { shallowMount } from '@vue/test-utils'; +import Metrics from 'ee/analytics/cycle_analytics/components/metrics.vue'; +import TimeMetricsCard from 'ee/analytics/cycle_analytics/components/time_metrics_card.vue'; +import { OVERVIEW_METRICS } from 'ee/analytics/cycle_analytics/constants'; +import { group } from '../mock_data'; + +describe('Metrics', () => { + const { full_path: groupPath } = group; + let wrapper; + + const createComponent = ({ requestParams = {} } = {}) => { + return shallowMount(Metrics, { + propsData: { + groupPath, + requestParams, + }, + }); + }; + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findTimeMetricsAtIndex = index => wrapper.findAll(TimeMetricsCard).at(index); + + it.each` + metric | index | requestType + ${'time summary'} | ${0} | ${OVERVIEW_METRICS.TIME_SUMMARY} + ${'recent activity'} | ${1} | ${OVERVIEW_METRICS.RECENT_ACTIVITY} + `('renders the $metric', ({ index, requestType }) => { + const card = findTimeMetricsAtIndex(index); + expect(card.props('requestType')).toBe(requestType); + expect(card.html()).toMatchSnapshot(); + }); +}); diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/recent_activity_card_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/recent_activity_card_spec.js deleted file mode 100644 index a0dd5d8b253860673e6fc0c3e9fd10244ed69b4e..0000000000000000000000000000000000000000 --- a/ee/spec/frontend/analytics/cycle_analytics/components/recent_activity_card_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { mount } from '@vue/test-utils'; -import RecentActivityCard from 'ee/analytics/cycle_analytics/components/recent_activity_card.vue'; -import Api from 'ee/api'; -import createFlash from '~/flash'; -import { group, recentActivityData } from '../mock_data'; - -jest.mock('~/flash'); - -describe('RecentActivityCard', () => { - const { full_path: groupPath } = group; - let wrapper; - - const createComponent = (additionalParams = {}) => { - return mount(RecentActivityCard, { - propsData: { - groupPath, - additionalParams, - }, - }); - }; - - beforeEach(() => { - jest.spyOn(Api, 'cycleAnalyticsSummaryData').mockResolvedValue({ data: recentActivityData }); - - wrapper = createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('matches the snapshot', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('fetches the recent activity data', () => { - expect(Api.cycleAnalyticsSummaryData).toHaveBeenCalledWith(groupPath, {}); - }); - - describe('with a failing request', () => { - beforeEach(() => { - jest.spyOn(Api, 'cycleAnalyticsSummaryData').mockRejectedValue(); - - wrapper = createComponent(); - }); - - it('should render an error message', () => { - expect(createFlash).toHaveBeenCalledWith( - 'There was an error while fetching value stream analytics recent activity data.', - ); - }); - }); - - describe('with additional params', () => { - beforeEach(() => { - wrapper = createComponent({ - 'project_ids[]': [1], - created_after: '2020-01-01', - created_before: '2020-02-01', - }); - }); - - it('sends additional parameters as query paremeters', () => { - expect(Api.cycleAnalyticsSummaryData).toHaveBeenCalledWith(groupPath, { - 'project_ids[]': [1], - created_after: '2020-01-01', - created_before: '2020-02-01', - }); - }); - }); -}); diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/time_metrics_card_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/time_metrics_card_spec.js index b83e4960487b0d206f5c84e6f4b9e43b2cf9f774..221a7333aa8ebf337e3ca067a84620f5d67ebe2c 100644 --- a/ee/spec/frontend/analytics/cycle_analytics/components/time_metrics_card_spec.js +++ b/ee/spec/frontend/analytics/cycle_analytics/components/time_metrics_card_spec.js @@ -1,8 +1,9 @@ import { shallowMount } from '@vue/test-utils'; import TimeMetricsCard from 'ee/analytics/cycle_analytics/components/time_metrics_card.vue'; +import { OVERVIEW_METRICS } from 'ee/analytics/cycle_analytics/constants'; import Api from 'ee/api'; +import { group, timeMetricsData, recentActivityData } from '../mock_data'; import createFlash from '~/flash'; -import { group, timeMetricsData } from '../mock_data'; jest.mock('~/flash'); @@ -10,62 +11,78 @@ describe('TimeMetricsCard', () => { const { full_path: groupPath } = group; let wrapper; - const createComponent = ({ additionalParams = {} } = {}) => { + const template = ` + <div slot-scope="{ metrics }"> + <span v-for="metric in metrics">{{metric.value}} {{metric.unit}}</span> + </div>`; + + const createComponent = ({ additionalParams = {}, requestType } = {}) => { return shallowMount(TimeMetricsCard, { propsData: { groupPath, additionalParams, + requestType, }, - slots: { - default: 'mockMetricCard', + scopedSlots: { + default: template, }, }); }; - beforeEach(() => { - jest.spyOn(Api, 'cycleAnalyticsTimeSummaryData').mockResolvedValue({ data: timeMetricsData }); - - wrapper = createComponent(); - }); + describe.each` + metric | requestType | request | data + ${'Recent activity'} | ${OVERVIEW_METRICS.RECENT_ACTIVITY} | ${'cycleAnalyticsSummaryData'} | ${recentActivityData} + ${'Time summary'} | ${OVERVIEW_METRICS.TIME_SUMMARY} | ${'cycleAnalyticsTimeSummaryData'} | ${timeMetricsData} + `('$metric', ({ requestType, request, data, metric }) => { + beforeEach(() => { + jest.spyOn(Api, request).mockResolvedValue({ data }); + wrapper = createComponent({ requestType }); + }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - it('fetches the time metrics data', () => { - expect(Api.cycleAnalyticsTimeSummaryData).toHaveBeenCalledWith(groupPath, {}); - }); + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); - describe('with a failing request', () => { - beforeEach(() => { - jest.spyOn(Api, 'cycleAnalyticsTimeSummaryData').mockRejectedValue(); + it(`renders the ${metric} metric`, () => { + expect(wrapper.html()).toMatchSnapshot(); + }); - wrapper = createComponent(); + it('fetches the metric data', () => { + expect(Api[request]).toHaveBeenCalledWith(groupPath, {}); }); - it('should render an error message', () => { - expect(createFlash).toHaveBeenCalledWith( - 'There was an error while fetching value stream analytics time summary data.', - ); + describe('with a failing request', () => { + beforeEach(() => { + jest.spyOn(Api, request).mockRejectedValue(); + wrapper = createComponent({ requestType }); + }); + + it('should render an error message', () => { + expect(createFlash).toHaveBeenCalledWith( + `There was an error while fetching value stream analytics ${metric.toLowerCase()} data.`, + ); + }); }); - }); - describe('with additional params', () => { - beforeEach(() => { - wrapper = createComponent({ - additionalParams: { + describe('with additional params', () => { + beforeEach(() => { + wrapper = createComponent({ + requestType, + additionalParams: { + 'project_ids[]': [1], + created_after: '2020-01-01', + created_before: '2020-02-01', + }, + }); + }); + + it('sends additional parameters as query paremeters', () => { + expect(Api[request]).toHaveBeenCalledWith(groupPath, { 'project_ids[]': [1], created_after: '2020-01-01', created_before: '2020-02-01', - }, - }); - }); - - it('sends additional parameters as query paremeters', () => { - expect(Api.cycleAnalyticsTimeSummaryData).toHaveBeenCalledWith(groupPath, { - 'project_ids[]': [1], - created_after: '2020-01-01', - created_before: '2020-02-01', + }); }); }); }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 497757c72d0680e787baeabe9b5173f8bb2f58f8..f06e59d18e6afc3724909459070117b172cc8be0 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -24830,6 +24830,9 @@ msgstr "" msgid "There was an error while fetching the table data." msgstr "" +msgid "There was an error while fetching value stream analytics %{requestTypeName} data." +msgstr "" + msgid "There was an error while fetching value stream analytics data." msgstr "" @@ -24839,12 +24842,6 @@ msgstr "" msgid "There was an error while fetching value stream analytics duration median data." msgstr "" -msgid "There was an error while fetching value stream analytics recent activity data." -msgstr "" - -msgid "There was an error while fetching value stream analytics time summary data." -msgstr "" - msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." msgstr "" @@ -29728,6 +29725,9 @@ msgstr "" msgid "quick actions" msgstr "" +msgid "recent activity" +msgstr "" + msgid "register" msgstr "" @@ -29892,6 +29892,9 @@ msgstr "" msgid "this document" msgstr "" +msgid "time summary" +msgstr "" + msgid "to help your contributors communicate effectively!" msgstr ""