Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/cycle_analytics')
-rw-r--r--spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap28
-rw-r--r--spec/frontend/cycle_analytics/base_spec.js265
-rw-r--r--spec/frontend/cycle_analytics/filter_bar_spec.js223
-rw-r--r--spec/frontend/cycle_analytics/formatted_stage_count_spec.js34
-rw-r--r--spec/frontend/cycle_analytics/mock_data.js261
-rw-r--r--spec/frontend/cycle_analytics/path_navigation_spec.js150
-rw-r--r--spec/frontend/cycle_analytics/stage_table_spec.js371
-rw-r--r--spec/frontend/cycle_analytics/store/actions_spec.js518
-rw-r--r--spec/frontend/cycle_analytics/store/getters_spec.js42
-rw-r--r--spec/frontend/cycle_analytics/store/mutations_spec.js132
-rw-r--r--spec/frontend/cycle_analytics/total_time_spec.js45
-rw-r--r--spec/frontend/cycle_analytics/utils_spec.js171
-rw-r--r--spec/frontend/cycle_analytics/value_stream_filters_spec.js91
-rw-r--r--spec/frontend/cycle_analytics/value_stream_metrics_spec.js185
14 files changed, 0 insertions, 2516 deletions
diff --git a/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap b/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap
deleted file mode 100644
index 92927ef16ec..00000000000
--- a/spec/frontend/cycle_analytics/__snapshots__/total_time_spec.js.snap
+++ /dev/null
@@ -1,28 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TotalTime with a blank object should render -- 1`] = `"<span> -- </span>"`;
-
-exports[`TotalTime with a valid time object with {"days": 3, "mins": 47, "seconds": 3} 1`] = `
-"<span>
- 3 <span>days</span></span>"
-`;
-
-exports[`TotalTime with a valid time object with {"hours": 7, "mins": 20, "seconds": 10} 1`] = `
-"<span>
- 7 <span>hrs</span></span>"
-`;
-
-exports[`TotalTime with a valid time object with {"hours": 23, "mins": 10} 1`] = `
-"<span>
- 23 <span>hrs</span></span>"
-`;
-
-exports[`TotalTime with a valid time object with {"mins": 47, "seconds": 3} 1`] = `
-"<span>
- 47 <span>mins</span></span>"
-`;
-
-exports[`TotalTime with a valid time object with {"seconds": 35} 1`] = `
-"<span>
- 35 <span>s</span></span>"
-`;
diff --git a/spec/frontend/cycle_analytics/base_spec.js b/spec/frontend/cycle_analytics/base_spec.js
deleted file mode 100644
index 013bea671a8..00000000000
--- a/spec/frontend/cycle_analytics/base_spec.js
+++ /dev/null
@@ -1,265 +0,0 @@
-import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
-import BaseComponent from '~/cycle_analytics/components/base.vue';
-import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
-import StageTable from '~/cycle_analytics/components/stage_table.vue';
-import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
-import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
-import initState from '~/cycle_analytics/store/state';
-import {
- transformedProjectStagePathData,
- selectedStage,
- issueEvents,
- createdBefore,
- createdAfter,
- currentGroup,
- stageCounts,
- initialPaginationState as pagination,
-} from './mock_data';
-
-const selectedStageEvents = issueEvents.events;
-const noDataSvgPath = 'path/to/no/data';
-const noAccessSvgPath = 'path/to/no/access';
-const selectedStageCount = stageCounts[selectedStage.id];
-const fullPath = 'full/path/to/foo';
-
-Vue.use(Vuex);
-
-let wrapper;
-
-const { id: groupId, path: groupPath } = currentGroup;
-const defaultState = {
- currentGroup,
- createdBefore,
- createdAfter,
- stageCounts,
- endpoints: { fullPath, groupId, groupPath },
-};
-
-function createStore({ initialState = {}, initialGetters = {} }) {
- return new Vuex.Store({
- state: {
- ...initState(),
- ...defaultState,
- ...initialState,
- },
- getters: {
- pathNavigationData: () => transformedProjectStagePathData,
- filterParams: () => ({
- created_after: createdAfter,
- created_before: createdBefore,
- }),
- ...initialGetters,
- },
- });
-}
-
-function createComponent({ initialState, initialGetters } = {}) {
- return extendedWrapper(
- shallowMount(BaseComponent, {
- store: createStore({ initialState, initialGetters }),
- propsData: {
- noDataSvgPath,
- noAccessSvgPath,
- },
- stubs: {
- StageTable,
- },
- }),
- );
-}
-
-const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
-const findPathNavigation = () => wrapper.findComponent(PathNavigation);
-const findFilters = () => wrapper.findComponent(ValueStreamFilters);
-const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
-const findStageTable = () => wrapper.findComponent(StageTable);
-const findStageEvents = () => findStageTable().props('stageEvents');
-const findEmptyStageTitle = () => wrapper.findComponent(GlEmptyState).props('title');
-const findPagination = () => wrapper.findByTestId('vsa-stage-pagination');
-
-const hasMetricsRequests = (reqs) => {
- const foundReqs = findOverviewMetrics().props('requests');
- expect(foundReqs.length).toEqual(reqs.length);
- expect(foundReqs.map(({ name }) => name)).toEqual(reqs);
-};
-
-describe('Value stream analytics component', () => {
- beforeEach(() => {
- wrapper = createComponent({ initialState: { selectedStage, selectedStageEvents, pagination } });
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('renders the path navigation component', () => {
- expect(findPathNavigation().exists()).toBe(true);
- });
-
- it('receives the stages formatted for the path navigation', () => {
- expect(findPathNavigation().props('stages')).toBe(transformedProjectStagePathData);
- });
-
- it('renders the overview metrics', () => {
- expect(findOverviewMetrics().exists()).toBe(true);
- });
-
- it('passes requests prop to the metrics component', () => {
- hasMetricsRequests(['recent activity']);
- });
-
- it('renders the stage table', () => {
- expect(findStageTable().exists()).toBe(true);
- });
-
- it('passes the selected stage count to the stage table', () => {
- expect(findStageTable().props('stageCount')).toBe(selectedStageCount);
- });
-
- it('renders the stage table events', () => {
- expect(findStageEvents()).toEqual(selectedStageEvents);
- });
-
- it('renders the filters', () => {
- expect(findFilters().exists()).toBe(true);
- });
-
- it('displays the date range selector and hides the project selector', () => {
- expect(findFilters().props()).toMatchObject({
- hasProjectFilter: false,
- hasDateRangeFilter: true,
- });
- });
-
- it('passes the paths to the filter bar', () => {
- expect(findFilters().props()).toEqual({
- groupId,
- groupPath,
- endDate: createdBefore,
- hasDateRangeFilter: true,
- hasProjectFilter: false,
- selectedProjects: [],
- startDate: createdAfter,
- });
- });
-
- it('does not render the loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
-
- it('renders pagination', () => {
- expect(findPagination().exists()).toBe(true);
- });
-
- describe('with `cycleAnalyticsForGroups=true` license', () => {
- beforeEach(() => {
- wrapper = createComponent({ initialState: { features: { cycleAnalyticsForGroups: true } } });
- });
-
- it('passes requests prop to the metrics component', () => {
- hasMetricsRequests(['time summary', 'recent activity']);
- });
- });
-
- describe('isLoading = true', () => {
- beforeEach(() => {
- wrapper = createComponent({
- initialState: { isLoading: true },
- });
- });
-
- it('renders the path navigation component with prop `loading` set to true', () => {
- expect(findPathNavigation().props('loading')).toBe(true);
- });
-
- it('does not render the stage table', () => {
- expect(findStageTable().exists()).toBe(false);
- });
-
- it('renders the overview metrics', () => {
- expect(findOverviewMetrics().exists()).toBe(true);
- });
-
- it('renders the loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
- });
-
- describe('isLoadingStage = true', () => {
- beforeEach(() => {
- wrapper = createComponent({
- initialState: { isLoadingStage: true },
- });
- });
-
- it('renders the stage table with a loading icon', () => {
- const tableWrapper = findStageTable();
- expect(tableWrapper.exists()).toBe(true);
- expect(tableWrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
- });
-
- it('renders the path navigation loading state', () => {
- expect(findPathNavigation().props('loading')).toBe(true);
- });
- });
-
- describe('isEmptyStage = true', () => {
- const emptyStageParams = {
- isEmptyStage: true,
- selectedStage: { ...selectedStage, emptyStageText: 'This stage is empty' },
- };
- beforeEach(() => {
- wrapper = createComponent({ initialState: emptyStageParams });
- });
-
- it('renders the empty stage with `Not enough data` message', () => {
- expect(findEmptyStageTitle()).toBe(NOT_ENOUGH_DATA_ERROR);
- });
-
- describe('with a selectedStageError', () => {
- beforeEach(() => {
- wrapper = createComponent({
- initialState: {
- ...emptyStageParams,
- selectedStageError: 'There is too much data to calculate',
- },
- });
- });
-
- it('renders the empty stage with `There is too much data to calculate` message', () => {
- expect(findEmptyStageTitle()).toBe('There is too much data to calculate');
- });
- });
- });
-
- describe('without a selected stage', () => {
- beforeEach(() => {
- wrapper = createComponent({
- initialGetters: { pathNavigationData: () => [] },
- initialState: { selectedStage: null, isEmptyStage: true },
- });
- });
-
- it('renders the stage table', () => {
- expect(findStageTable().exists()).toBe(true);
- });
-
- it('does not render the path navigation', () => {
- expect(findPathNavigation().exists()).toBe(false);
- });
-
- it('does not render the stage table events', () => {
- expect(findStageEvents()).toHaveLength(0);
- });
-
- it('does not render the loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/filter_bar_spec.js b/spec/frontend/cycle_analytics/filter_bar_spec.js
deleted file mode 100644
index 36933790cf7..00000000000
--- a/spec/frontend/cycle_analytics/filter_bar_spec.js
+++ /dev/null
@@ -1,223 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import Vuex from 'vuex';
-import {
- filterMilestones,
- filterLabels,
-} from 'jest/vue_shared/components/filtered_search_bar/store/modules/filters/mock_data';
-import FilterBar from '~/cycle_analytics/components/filter_bar.vue';
-import storeConfig from '~/cycle_analytics/store';
-import * as commonUtils from '~/lib/utils/common_utils';
-import * as urlUtils from '~/lib/utils/url_utility';
-import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-import * as utils from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
-import initialFiltersState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
-import UrlSync from '~/vue_shared/components/url_sync.vue';
-
-Vue.use(Vuex);
-
-const milestoneTokenType = 'milestone';
-const labelsTokenType = 'labels';
-const authorTokenType = 'author';
-const assigneesTokenType = 'assignees';
-
-const initialFilterBarState = {
- selectedMilestone: null,
- selectedAuthor: null,
- selectedAssigneeList: null,
- selectedLabelList: null,
-};
-
-const defaultParams = {
- milestone_title: null,
- 'not[milestone_title]': null,
- author_username: null,
- 'not[author_username]': null,
- assignee_username: null,
- 'not[assignee_username]': null,
- label_name: null,
- 'not[label_name]': null,
-};
-
-async function shouldMergeUrlParams(wrapper, result) {
- await nextTick();
- expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(result, window.location.href, {
- spreadArrays: true,
- });
- expect(commonUtils.historyPushState).toHaveBeenCalled();
-}
-
-describe('Filter bar', () => {
- let wrapper;
- let store;
- let mock;
-
- let setFiltersMock;
-
- const createStore = (initialState = {}) => {
- setFiltersMock = jest.fn();
-
- return new Vuex.Store({
- modules: {
- filters: {
- namespaced: true,
- state: {
- ...initialFiltersState(),
- ...initialState,
- },
- actions: {
- setFilters: setFiltersMock,
- },
- },
- },
- });
- };
-
- const createComponent = (initialStore) => {
- return shallowMount(FilterBar, {
- store: initialStore,
- propsData: {
- groupPath: 'foo',
- },
- stubs: {
- UrlSync,
- },
- });
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
- const selectedMilestone = [filterMilestones[0]];
- const selectedLabelList = [filterLabels[0]];
-
- const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBar);
- const getSearchToken = (type) =>
- findFilteredSearch()
- .props('tokens')
- .find((token) => token.type === type);
-
- describe('default', () => {
- beforeEach(() => {
- store = createStore();
- wrapper = createComponent(store);
- });
-
- it('renders FilteredSearchBar component', () => {
- expect(findFilteredSearch().exists()).toBe(true);
- });
- });
-
- describe('when the state has data', () => {
- beforeEach(() => {
- store = createStore({
- milestones: { data: selectedMilestone },
- labels: { data: selectedLabelList },
- authors: { data: [] },
- assignees: { data: [] },
- });
- wrapper = createComponent(store);
- });
-
- it('displays the milestone and label token', () => {
- const tokens = findFilteredSearch().props('tokens');
-
- expect(tokens).toHaveLength(4);
- expect(tokens[0].type).toBe(milestoneTokenType);
- expect(tokens[1].type).toBe(labelsTokenType);
- expect(tokens[2].type).toBe(authorTokenType);
- expect(tokens[3].type).toBe(assigneesTokenType);
- });
-
- it('provides the initial milestone token', () => {
- const { initialMilestones: milestoneToken } = getSearchToken(milestoneTokenType);
-
- expect(milestoneToken).toHaveLength(selectedMilestone.length);
- });
-
- it('provides the initial label token', () => {
- const { initialLabels: labelToken } = getSearchToken(labelsTokenType);
-
- expect(labelToken).toHaveLength(selectedLabelList.length);
- });
- });
-
- describe('when the user interacts', () => {
- beforeEach(() => {
- store = createStore({
- milestones: { data: filterMilestones },
- labels: { data: filterLabels },
- });
- wrapper = createComponent(store);
- jest.spyOn(utils, 'processFilters');
- });
-
- it('clicks on the search button, setFilters is dispatched', () => {
- const filters = [
- { type: 'milestone', value: { data: selectedMilestone[0].title, operator: '=' } },
- { type: 'labels', value: { data: selectedLabelList[0].title, operator: '=' } },
- ];
-
- findFilteredSearch().vm.$emit('onFilter', filters);
-
- expect(utils.processFilters).toHaveBeenCalledWith(filters);
-
- expect(setFiltersMock).toHaveBeenCalledWith(expect.anything(), {
- selectedLabelList: [{ value: selectedLabelList[0].title, operator: '=' }],
- selectedMilestone: { value: selectedMilestone[0].title, operator: '=' },
- selectedAssigneeList: [],
- selectedAuthor: null,
- });
- });
- });
-
- describe.each([
- ['selectedMilestone', 'milestone_title', { value: '12.0', operator: '=' }, '12.0'],
- ['selectedAuthor', 'author_username', { value: 'rootUser', operator: '=' }, 'rootUser'],
- [
- 'selectedLabelList',
- 'label_name',
- [
- { value: 'Afternix', operator: '=' },
- { value: 'Brouceforge', operator: '=' },
- ],
- ['Afternix', 'Brouceforge'],
- ],
- [
- 'selectedAssigneeList',
- 'assignee_username',
- [
- { value: 'rootUser', operator: '=' },
- { value: 'secondaryUser', operator: '=' },
- ],
- ['rootUser', 'secondaryUser'],
- ],
- ])('with a %s updates the %s url parameter', (stateKey, paramKey, payload, result) => {
- beforeEach(() => {
- commonUtils.historyPushState = jest.fn();
- urlUtils.mergeUrlParams = jest.fn();
-
- mock = new MockAdapter(axios);
- wrapper = createComponent(storeConfig);
-
- wrapper.vm.$store.dispatch('filters/setFilters', {
- ...initialFilterBarState,
- [stateKey]: payload,
- });
- });
- it(`sets the ${paramKey} url parameter`, () => {
- return shouldMergeUrlParams(wrapper, {
- ...defaultParams,
- [paramKey]: result,
- });
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/formatted_stage_count_spec.js b/spec/frontend/cycle_analytics/formatted_stage_count_spec.js
deleted file mode 100644
index 1228b8511ea..00000000000
--- a/spec/frontend/cycle_analytics/formatted_stage_count_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Component from '~/cycle_analytics/components/formatted_stage_count.vue';
-
-describe('Formatted Stage Count', () => {
- let wrapper = null;
-
- const createComponent = (stageCount = null) => {
- wrapper = shallowMount(Component, {
- propsData: {
- stageCount,
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it.each`
- stageCount | expectedOutput
- ${null} | ${'-'}
- ${1} | ${'1 item'}
- ${10} | ${'10 items'}
- ${1000} | ${'1,000 items'}
- ${1001} | ${'1,000+ items'}
- `('returns "$expectedOutput" for stageCount=$stageCount', ({ stageCount, expectedOutput }) => {
- createComponent(stageCount);
- expect(wrapper.text()).toContain(expectedOutput);
- });
-});
diff --git a/spec/frontend/cycle_analytics/mock_data.js b/spec/frontend/cycle_analytics/mock_data.js
deleted file mode 100644
index 02666260cdb..00000000000
--- a/spec/frontend/cycle_analytics/mock_data.js
+++ /dev/null
@@ -1,261 +0,0 @@
-import valueStreamAnalyticsStages from 'test_fixtures/projects/analytics/value_stream_analytics/stages.json';
-import issueStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/issue.json';
-import planStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/plan.json';
-import reviewStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/review.json';
-import codeStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/code.json';
-import testStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/test.json';
-import stagingStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/staging.json';
-
-import { TEST_HOST } from 'helpers/test_constants';
-import {
- DEFAULT_VALUE_STREAM,
- PAGINATION_TYPE,
- PAGINATION_SORT_DIRECTION_DESC,
- PAGINATION_SORT_FIELD_END_EVENT,
-} from '~/cycle_analytics/constants';
-import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { getDateInPast } from '~/lib/utils/datetime_utility';
-
-const DEFAULT_DAYS_IN_PAST = 30;
-export const createdBefore = new Date(2019, 0, 14);
-export const createdAfter = getDateInPast(createdBefore, DEFAULT_DAYS_IN_PAST);
-
-export const deepCamelCase = (obj) => convertObjectPropsToCamelCase(obj, { deep: true });
-
-export const getStageByTitle = (stages, title) =>
- stages.find((stage) => stage.title && stage.title.toLowerCase().trim() === title) || {};
-
-export const defaultStages = ['issue', 'plan', 'review', 'code', 'test', 'staging'];
-
-const stageFixtures = {
- issue: issueStageFixtures,
- plan: planStageFixtures,
- review: reviewStageFixtures,
- code: codeStageFixtures,
- test: testStageFixtures,
- staging: stagingStageFixtures,
-};
-
-export const summary = [
- { value: '20', title: 'New Issues' },
- { value: null, title: 'Commits' },
- { value: null, title: 'Deploys' },
- { value: null, title: 'Deployment Frequency', unit: '/day' },
-];
-
-export const issueStage = {
- id: 'issue',
- title: 'Issue',
- name: 'issue',
- legend: '',
- description: 'Time before an issue gets scheduled',
- value: null,
-};
-
-export const planStage = {
- id: 'plan',
- title: 'Plan',
- name: 'plan',
- legend: '',
- description: 'Time before an issue starts implementation',
- value: 75600,
-};
-
-export const codeStage = {
- id: 'code',
- title: 'Code',
- name: 'code',
- legend: '',
- description: 'Time until first merge request',
- value: 172800,
-};
-
-export const testStage = {
- id: 'test',
- title: 'Test',
- name: 'test',
- legend: '',
- description: 'Total test time for all commits/merges',
- value: 17550,
-};
-
-export const reviewStage = {
- id: 'review',
- title: 'Review',
- name: 'review',
- legend: '',
- description: 'Time between merge request creation and merge/close',
- value: null,
-};
-
-export const stagingStage = {
- id: 'staging',
- title: 'Staging',
- name: 'staging',
- legend: '',
- description: 'From merge request merge until deploy to production',
- value: 172800,
-};
-
-export const selectedStage = {
- ...issueStage,
- value: null,
- active: false,
- emptyStageText:
- 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
-
- slug: 'issue',
-};
-
-export const convertedData = {
- summary: [
- { value: '20', title: 'New Issues' },
- { value: '-', title: 'Commits' },
- { value: '-', title: 'Deploys' },
- { value: '-', title: 'Deployment Frequency', unit: '/day' },
- ],
-};
-
-export const rawIssueEvents = stageFixtures.issue;
-export const issueEvents = deepCamelCase(rawIssueEvents);
-export const reviewEvents = deepCamelCase(stageFixtures.review);
-
-export const pathNavIssueMetric = 172800;
-
-export const rawStageCounts = [
- { id: 'issue', count: 6 },
- { id: 'plan', count: 6 },
- { id: 'code', count: 1 },
- { id: 'test', count: 5 },
- { id: 'review', count: 12 },
- { id: 'staging', count: 3 },
-];
-
-export const stageCounts = {
- code: 1,
- issue: 6,
- plan: 6,
- review: 12,
- staging: 3,
- test: 5,
-};
-
-export const rawStageMedians = [
- { id: 'issue', value: 172800 },
- { id: 'plan', value: 86400 },
- { id: 'review', value: 1036800 },
- { id: 'code', value: 129600 },
- { id: 'test', value: 259200 },
- { id: 'staging', value: 388800 },
-];
-
-export const stageMedians = {
- issue: 172800,
- plan: 86400,
- review: 1036800,
- code: 129600,
- test: 259200,
- staging: 388800,
-};
-
-export const formattedStageMedians = {
- issue: '2d',
- plan: '1d',
- review: '1w',
- code: '1d',
- test: '3d',
- staging: '4d',
-};
-
-export const allowedStages = [issueStage, planStage, codeStage];
-
-export const transformedProjectStagePathData = [
- {
- metric: 172800,
- selected: true,
- stageCount: 6,
- icon: null,
- id: 'issue',
- title: 'Issue',
- name: 'issue',
- legend: '',
- description: 'Time before an issue gets scheduled',
- value: null,
- },
- {
- metric: 86400,
- selected: false,
- stageCount: 6,
- icon: null,
- id: 'plan',
- title: 'Plan',
- name: 'plan',
- legend: '',
- description: 'Time before an issue starts implementation',
- value: 75600,
- },
- {
- metric: 129600,
- selected: false,
- stageCount: 1,
- icon: null,
- id: 'code',
- title: 'Code',
- name: 'code',
- legend: '',
- description: 'Time until first merge request',
- value: 172800,
- },
-];
-
-export const selectedValueStream = DEFAULT_VALUE_STREAM;
-
-export const group = {
- id: 1,
- name: 'foo',
- path: 'foo',
- full_path: 'foo',
- avatar_url: `${TEST_HOST}/images/home/nasa.svg`,
-};
-
-export const currentGroup = convertObjectPropsToCamelCase(group, { deep: true });
-
-export const selectedProjects = [
- {
- id: 'gid://gitlab/Project/1',
- name: 'cool project',
- pathWithNamespace: 'group/cool-project',
- avatarUrl: null,
- },
- {
- id: 'gid://gitlab/Project/2',
- name: 'another cool project',
- pathWithNamespace: 'group/another-cool-project',
- avatarUrl: null,
- },
-];
-
-export const rawValueStreamStages = valueStreamAnalyticsStages.stages;
-
-export const valueStreamStages = rawValueStreamStages.map((s) =>
- convertObjectPropsToCamelCase(s, { deep: true }),
-);
-
-export const initialPaginationQuery = {
- page: 15,
- sort: PAGINATION_SORT_FIELD_END_EVENT,
- direction: PAGINATION_SORT_DIRECTION_DESC,
-};
-
-export const initialPaginationState = {
- ...initialPaginationQuery,
- page: null,
- hasNextPage: false,
-};
-
-export const basePaginationResult = {
- pagination: PAGINATION_TYPE,
- sort: PAGINATION_SORT_FIELD_END_EVENT,
- direction: PAGINATION_SORT_DIRECTION_DESC,
- page: null,
-};
diff --git a/spec/frontend/cycle_analytics/path_navigation_spec.js b/spec/frontend/cycle_analytics/path_navigation_spec.js
deleted file mode 100644
index fec1526359c..00000000000
--- a/spec/frontend/cycle_analytics/path_navigation_spec.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import { GlPath, GlSkeletonLoader } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import Component from '~/cycle_analytics/components/path_navigation.vue';
-import { transformedProjectStagePathData, selectedStage } from './mock_data';
-
-describe('Project PathNavigation', () => {
- let wrapper = null;
- let trackingSpy = null;
-
- const createComponent = (props) => {
- return extendedWrapper(
- mount(Component, {
- propsData: {
- stages: transformedProjectStagePathData,
- selectedStage,
- loading: false,
- ...props,
- },
- }),
- );
- };
-
- const findPathNavigation = () => {
- return wrapper.findByTestId('gl-path-nav');
- };
-
- const findPathNavigationItems = () => {
- return findPathNavigation().findAll('li');
- };
-
- const findPathNavigationTitles = () => {
- return findPathNavigation()
- .findAll('li button')
- .wrappers.map((w) => w.html());
- };
-
- const clickItemAt = (index) => {
- findPathNavigationItems().at(index).find('button').trigger('click');
- };
-
- const pathItemContent = () => findPathNavigationItems().wrappers.map(extendedWrapper);
- const firstPopover = () => wrapper.findAllByTestId('stage-item-popover').at(0);
-
- beforeEach(() => {
- wrapper = createComponent();
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('displays correctly', () => {
- it('has the correct props', () => {
- expect(wrapper.findComponent(GlPath).props('items')).toMatchObject(
- transformedProjectStagePathData,
- );
- });
-
- it('contains all the expected stages', () => {
- const stageContent = findPathNavigationTitles();
- transformedProjectStagePathData.forEach((stage, index) => {
- expect(stageContent[index]).toContain(stage.title);
- });
- });
-
- describe('loading', () => {
- describe('is false', () => {
- it('displays the gl-path component', () => {
- expect(wrapper.findComponent(GlPath).exists()).toBe(true);
- });
-
- it('hides the gl-skeleton-loading component', () => {
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
- });
-
- it('renders each stage', () => {
- const result = findPathNavigationTitles();
- expect(result.length).toBe(transformedProjectStagePathData.length);
- });
-
- it('renders each stage with its median', () => {
- const result = findPathNavigationTitles();
- transformedProjectStagePathData.forEach(({ title, metric }, index) => {
- expect(result[index]).toContain(title);
- expect(result[index]).toContain(metric.toString());
- });
- });
-
- describe('popovers', () => {
- beforeEach(() => {
- wrapper = createComponent({ stages: transformedProjectStagePathData });
- });
-
- it('renders popovers for all stages', () => {
- pathItemContent().forEach((stage) => {
- expect(stage.findByTestId('stage-item-popover').exists()).toBe(true);
- });
- });
-
- it('shows the median stage time for the first stage item', () => {
- expect(firstPopover().text()).toContain('Stage time (median)');
- });
- });
- });
-
- describe('is true', () => {
- beforeEach(() => {
- wrapper = createComponent({ loading: true });
- });
-
- it('hides the gl-path component', () => {
- expect(wrapper.findComponent(GlPath).exists()).toBe(false);
- });
-
- it('displays the gl-skeleton-loading component', () => {
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
- });
- });
- });
- });
-
- describe('event handling', () => {
- it('emits the selected event', () => {
- expect(wrapper.emitted('selected')).toBeUndefined();
-
- clickItemAt(0);
- clickItemAt(1);
- clickItemAt(2);
-
- expect(wrapper.emitted().selected).toEqual([
- [transformedProjectStagePathData[0]],
- [transformedProjectStagePathData[1]],
- [transformedProjectStagePathData[2]],
- ]);
- });
-
- it('sends tracking information', () => {
- clickItemAt(0);
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_path_navigation', {
- extra: { stage_id: selectedStage.slug },
- });
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/stage_table_spec.js b/spec/frontend/cycle_analytics/stage_table_spec.js
deleted file mode 100644
index 473e1d5b664..00000000000
--- a/spec/frontend/cycle_analytics/stage_table_spec.js
+++ /dev/null
@@ -1,371 +0,0 @@
-import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import StageTable from '~/cycle_analytics/components/stage_table.vue';
-import { PAGINATION_SORT_FIELD_DURATION } from '~/cycle_analytics/constants';
-import { issueEvents, issueStage, reviewStage, reviewEvents } from './mock_data';
-
-let wrapper = null;
-let trackingSpy = null;
-
-const noDataSvgPath = 'path/to/no/data';
-const emptyStateTitle = 'Too much data';
-const notEnoughDataError = "We don't have enough data to show this stage.";
-const issueEventItems = issueEvents.events;
-const reviewEventItems = reviewEvents.events;
-const [firstIssueEvent] = issueEventItems;
-const [firstReviewEvent] = reviewEventItems;
-const pagination = { page: 1, hasNextPage: true };
-
-const findStageEvents = () => wrapper.findAllByTestId('vsa-stage-event');
-const findPagination = () => wrapper.findByTestId('vsa-stage-pagination');
-const findTable = () => wrapper.findComponent(GlTable);
-const findTableHead = () => wrapper.find('thead');
-const findTableHeadColumns = () => findTableHead().findAll('th');
-const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title');
-const findStageEventLink = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-link');
-const findStageTime = () => wrapper.findByTestId('vsa-stage-event-time');
-const findStageLastEvent = () => wrapper.findByTestId('vsa-stage-last-event');
-const findIcon = (name) => wrapper.findByTestId(`${name}-icon`);
-
-function createComponent(props = {}, shallow = false) {
- const func = shallow ? shallowMount : mount;
- return extendedWrapper(
- func(StageTable, {
- propsData: {
- isLoading: false,
- stageEvents: issueEventItems,
- noDataSvgPath,
- selectedStage: issueStage,
- pagination,
- ...props,
- },
- stubs: {
- GlLoadingIcon,
- GlEmptyState,
- },
- }),
- );
-}
-
-describe('StageTable', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('is loaded with data', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- it('will render the correct events', () => {
- const evs = findStageEvents();
- expect(evs).toHaveLength(issueEventItems.length);
-
- const titles = evs.wrappers.map((ev) => findStageEventTitle(ev).text());
- issueEventItems.forEach((ev, index) => {
- expect(titles[index]).toBe(ev.title);
- });
- });
-
- it('will not display the default data message', () => {
- expect(wrapper.html()).not.toContain(notEnoughDataError);
- });
- });
-
- describe('with minimal stage data', () => {
- beforeEach(() => {
- wrapper = createComponent({ currentStage: { title: 'New stage title' } });
- });
-
- it('will render the correct events', () => {
- const evs = findStageEvents();
- expect(evs).toHaveLength(issueEventItems.length);
-
- const titles = evs.wrappers.map((ev) => findStageEventTitle(ev).text());
- issueEventItems.forEach((ev, index) => {
- expect(titles[index]).toBe(ev.title);
- });
- });
-
- it('will not display the project name in the record link', () => {
- const evs = findStageEvents();
-
- const links = evs.wrappers.map((ev) => findStageEventLink(ev).text());
- issueEventItems.forEach((ev, index) => {
- expect(links[index]).toBe(`#${ev.iid}`);
- });
- });
- });
-
- describe('default event', () => {
- beforeEach(() => {
- wrapper = createComponent({
- stageEvents: [{ ...firstIssueEvent }],
- selectedStage: { ...issueStage, custom: false },
- });
- });
-
- it('will render the event title', () => {
- expect(wrapper.findByTestId('vsa-stage-event-title').text()).toBe(firstIssueEvent.title);
- });
-
- it('will set the workflow title to "Issues"', () => {
- expect(findTableHead().text()).toContain('Issues');
- });
-
- it('does not render the fork icon', () => {
- expect(findIcon('fork').exists()).toBe(false);
- });
-
- it('does not render the branch icon', () => {
- expect(findIcon('commit').exists()).toBe(false);
- });
-
- it('will render the total time', () => {
- const createdAt = firstIssueEvent.createdAt.replace(' ago', '');
- expect(findStageTime().text()).toBe(createdAt);
- });
-
- it('will render the end event', () => {
- expect(findStageLastEvent().text()).toBe(firstIssueEvent.endEventTimestamp);
- });
-
- it('will render the author', () => {
- expect(wrapper.findByTestId('vsa-stage-event-author').text()).toContain(
- firstIssueEvent.author.name,
- );
- });
-
- it('will render the created at date', () => {
- expect(wrapper.findByTestId('vsa-stage-event-date').text()).toContain(
- firstIssueEvent.createdAt,
- );
- });
- });
-
- describe('merge request event', () => {
- beforeEach(() => {
- wrapper = createComponent({
- stageEvents: [{ ...firstReviewEvent }],
- selectedStage: { ...reviewStage, custom: false },
- });
- });
-
- it('will set the workflow title to "Merge requests"', () => {
- expect(findTableHead().text()).toContain('Merge requests');
- expect(findTableHead().text()).not.toContain('Issues');
- });
- });
-
- describe('isLoading = true', () => {
- beforeEach(() => {
- wrapper = createComponent({ isLoading: true }, true);
- });
-
- it('will display the loading icon', () => {
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
- });
-
- it('will not display pagination', () => {
- expect(findPagination().exists()).toBe(false);
- });
- });
-
- describe('with no stageEvents', () => {
- beforeEach(() => {
- wrapper = createComponent({ stageEvents: [] });
- });
-
- it('will render the empty state', () => {
- expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
- });
-
- it('will display the default no data message', () => {
- expect(wrapper.html()).toContain(notEnoughDataError);
- });
-
- it('will not display the pagination component', () => {
- expect(findPagination().exists()).toBe(false);
- });
- });
-
- describe('emptyStateTitle set', () => {
- beforeEach(() => {
- wrapper = createComponent({ stageEvents: [], emptyStateTitle });
- });
-
- it('will display the custom message', () => {
- expect(wrapper.html()).not.toContain(notEnoughDataError);
- expect(wrapper.html()).toContain(emptyStateTitle);
- });
- });
-
- describe('includeProjectName set', () => {
- const fakenamespace = 'some/fake/path';
- beforeEach(() => {
- wrapper = createComponent({ includeProjectName: true });
- });
-
- it('will display the project name in the record link', () => {
- const evs = findStageEvents();
-
- const links = evs.wrappers.map((ev) => findStageEventLink(ev).text());
- issueEventItems.forEach((ev, index) => {
- expect(links[index]).toBe(`${ev.projectPath}#${ev.iid}`);
- });
- });
-
- describe.each`
- namespaceFullPath | hasFullPath
- ${'fake'} | ${false}
- ${fakenamespace} | ${true}
- `('with a namespace', ({ namespaceFullPath, hasFullPath }) => {
- let evs = null;
- let links = null;
-
- beforeEach(() => {
- wrapper = createComponent({
- includeProjectName: true,
- stageEvents: issueEventItems.map((ie) => ({ ...ie, namespaceFullPath })),
- });
-
- evs = findStageEvents();
- links = evs.wrappers.map((ev) => findStageEventLink(ev).text());
- });
-
- it(`with namespaceFullPath='${namespaceFullPath}' ${
- hasFullPath ? 'will' : 'does not'
- } include the namespace`, () => {
- issueEventItems.forEach((ev, index) => {
- if (hasFullPath) {
- expect(links[index]).toBe(`${namespaceFullPath}/${ev.projectPath}#${ev.iid}`);
- } else {
- expect(links[index]).toBe(`${ev.projectPath}#${ev.iid}`);
- }
- });
- });
- });
- });
-
- describe('Pagination', () => {
- beforeEach(() => {
- wrapper = createComponent();
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- wrapper.destroy();
- });
-
- it('will display the pagination component', () => {
- expect(findPagination().exists()).toBe(true);
- });
-
- it('clicking prev or next will emit an event', async () => {
- expect(wrapper.emitted('handleUpdatePagination')).toBeUndefined();
-
- findPagination().vm.$emit('input', 2);
- await nextTick();
-
- expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([{ page: 2 }]);
- });
-
- it('clicking prev or next will send tracking information', () => {
- findPagination().vm.$emit('input', 2);
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', { label: 'pagination' });
- });
-
- describe('with `hasNextPage=false', () => {
- beforeEach(() => {
- wrapper = createComponent({ pagination: { page: 1, hasNextPage: false } });
- });
-
- it('will not display the pagination component', () => {
- expect(findPagination().exists()).toBe(false);
- });
- });
- });
-
- describe('Sorting', () => {
- const triggerTableSort = (sortDesc = true) =>
- findTable().vm.$emit('sort-changed', {
- sortBy: PAGINATION_SORT_FIELD_DURATION,
- sortDesc,
- });
-
- beforeEach(() => {
- wrapper = createComponent();
- trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- });
-
- afterEach(() => {
- unmockTracking();
- wrapper.destroy();
- });
-
- it('can sort the end event or duration', () => {
- findTableHeadColumns()
- .wrappers.slice(1)
- .forEach((w) => {
- expect(w.attributes('aria-sort')).toBe('none');
- });
- });
-
- it('cannot be sorted by title', () => {
- findTableHeadColumns()
- .wrappers.slice(0, 1)
- .forEach((w) => {
- expect(w.attributes('aria-sort')).toBeUndefined();
- });
- });
-
- it('clicking a table column will send tracking information', () => {
- triggerTableSort();
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_button', {
- label: 'sort_duration_desc',
- });
- });
-
- it('clicking a table column will update the sort field', () => {
- expect(wrapper.emitted('handleUpdatePagination')).toBeUndefined();
- triggerTableSort();
-
- expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([
- {
- direction: 'desc',
- sort: 'duration',
- },
- ]);
- });
-
- it('with sortDesc=false will toggle the direction field', () => {
- expect(wrapper.emitted('handleUpdatePagination')).toBeUndefined();
- triggerTableSort(false);
-
- expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([
- {
- direction: 'asc',
- sort: 'duration',
- },
- ]);
- });
-
- describe('with sortable=false', () => {
- beforeEach(() => {
- wrapper = createComponent({ sortable: false });
- });
-
- it('cannot sort the table', () => {
- findTableHeadColumns().wrappers.forEach((w) => {
- expect(w.attributes('aria-sort')).toBeUndefined();
- });
- });
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/store/actions_spec.js b/spec/frontend/cycle_analytics/store/actions_spec.js
deleted file mode 100644
index 94b6de85a5c..00000000000
--- a/spec/frontend/cycle_analytics/store/actions_spec.js
+++ /dev/null
@@ -1,518 +0,0 @@
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
-import testAction from 'helpers/vuex_action_helper';
-import * as actions from '~/cycle_analytics/store/actions';
-import * as getters from '~/cycle_analytics/store/getters';
-import httpStatusCodes from '~/lib/utils/http_status';
-import {
- allowedStages,
- selectedStage,
- selectedValueStream,
- currentGroup,
- createdAfter,
- createdBefore,
- initialPaginationState,
- reviewEvents,
-} from '../mock_data';
-
-const { id: groupId, path: groupPath } = currentGroup;
-const mockMilestonesPath = 'mock-milestones.json';
-const mockLabelsPath = 'mock-labels.json';
-const mockRequestPath = 'some/cool/path';
-const mockFullPath = '/namespace/-/analytics/value_stream_analytics/value_streams';
-const mockEndpoints = {
- fullPath: mockFullPath,
- requestPath: mockRequestPath,
- labelsPath: mockLabelsPath,
- milestonesPath: mockMilestonesPath,
- groupId,
- groupPath,
-};
-const mockSetDateActionCommit = {
- payload: { createdAfter, createdBefore },
- type: 'SET_DATE_RANGE',
-};
-
-const defaultState = {
- ...getters,
- selectedValueStream,
- createdAfter,
- createdBefore,
- pagination: initialPaginationState,
-};
-
-describe('Project Value Stream Analytics actions', () => {
- let state;
- let mock;
-
- beforeEach(() => {
- state = { ...defaultState };
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- state = {};
- });
-
- const mutationTypes = (arr) => arr.map(({ type }) => type);
-
- describe.each`
- action | payload | expectedActions | expectedMutations
- ${'setDateRange'} | ${{ createdAfter, createdBefore }} | ${[{ type: 'refetchStageData' }]} | ${[mockSetDateActionCommit]}
- ${'setFilters'} | ${[]} | ${[{ type: 'refetchStageData' }]} | ${[]}
- ${'setSelectedStage'} | ${{ selectedStage }} | ${[{ type: 'refetchStageData' }]} | ${[{ type: 'SET_SELECTED_STAGE', payload: { selectedStage } }]}
- ${'setSelectedValueStream'} | ${{ selectedValueStream }} | ${[{ type: 'fetchValueStreamStages' }]} | ${[{ type: 'SET_SELECTED_VALUE_STREAM', payload: { selectedValueStream } }]}
- `('$action', ({ action, payload, expectedActions, expectedMutations }) => {
- const types = mutationTypes(expectedMutations);
- it(`will dispatch ${expectedActions} and commit ${types}`, () =>
- testAction({
- action: actions[action],
- state,
- payload,
- expectedMutations,
- expectedActions,
- }));
- });
-
- describe('initializeVsa', () => {
- const selectedAuthor = 'Author';
- const selectedMilestone = 'Milestone 1';
- const selectedAssigneeList = ['Assignee 1', 'Assignee 2'];
- const selectedLabelList = ['Label 1', 'Label 2'];
- const payload = {
- endpoints: mockEndpoints,
- selectedAuthor,
- selectedMilestone,
- selectedAssigneeList,
- selectedLabelList,
- selectedStage,
- };
- const mockFilterEndpoints = {
- groupEndpoint: 'foo',
- labelsEndpoint: mockLabelsPath,
- milestonesEndpoint: mockMilestonesPath,
- projectEndpoint: '/namespace/-/analytics/value_stream_analytics/value_streams',
- };
-
- it('will dispatch fetchValueStreams actions and commit SET_LOADING and INITIALIZE_VSA', () => {
- return testAction({
- action: actions.initializeVsa,
- state: {},
- payload,
- expectedMutations: [
- { type: 'INITIALIZE_VSA', payload },
- { type: 'SET_LOADING', payload: true },
- { type: 'SET_LOADING', payload: false },
- ],
- expectedActions: [
- { type: 'filters/setEndpoints', payload: mockFilterEndpoints },
- {
- type: 'filters/initialize',
- payload: { selectedAuthor, selectedMilestone, selectedAssigneeList, selectedLabelList },
- },
- { type: 'fetchValueStreams' },
- { type: 'setInitialStage', payload: selectedStage },
- ],
- });
- });
- });
-
- describe('setInitialStage', () => {
- beforeEach(() => {
- state = { ...state, stages: allowedStages };
- });
-
- describe('with a selected stage', () => {
- it('will commit `SET_SELECTED_STAGE` and fetchValueStreamStageData actions', () => {
- const fakeStage = { ...selectedStage, id: 'fake', name: 'fake-stae' };
- return testAction({
- action: actions.setInitialStage,
- state,
- payload: fakeStage,
- expectedMutations: [
- {
- type: 'SET_SELECTED_STAGE',
- payload: fakeStage,
- },
- ],
- expectedActions: [{ type: 'fetchValueStreamStageData' }],
- });
- });
- });
-
- describe('without a selected stage', () => {
- it('will select the first stage from the value stream', () => {
- const [firstStage] = allowedStages;
- testAction({
- action: actions.setInitialStage,
- state,
- payload: null,
- expectedMutations: [{ type: 'SET_SELECTED_STAGE', payload: firstStage }],
- expectedActions: [{ type: 'fetchValueStreamStageData' }],
- });
- });
- });
-
- describe('with no value stream stages available', () => {
- it('will return SET_NO_ACCESS_ERROR', () => {
- state = { ...state, stages: [] };
- testAction({
- action: actions.setInitialStage,
- state,
- payload: null,
- expectedMutations: [{ type: 'SET_NO_ACCESS_ERROR' }],
- expectedActions: [],
- });
- });
- });
- });
-
- describe('updateStageTablePagination', () => {
- beforeEach(() => {
- state = { ...state, selectedStage };
- });
-
- it(`will dispatch the "fetchStageData" action and commit the 'SET_PAGINATION' mutation`, () => {
- return testAction({
- action: actions.updateStageTablePagination,
- state,
- expectedMutations: [{ type: 'SET_PAGINATION' }],
- expectedActions: [{ type: 'fetchStageData', payload: selectedStage.id }],
- });
- });
- });
-
- describe('fetchStageData', () => {
- const mockStagePath = /value_streams\/\w+\/stages\/\w+\/records/;
- const headers = {
- 'X-Next-Page': 2,
- 'X-Page': 1,
- };
-
- beforeEach(() => {
- state = {
- ...defaultState,
- endpoints: mockEndpoints,
- selectedStage,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockStagePath).reply(httpStatusCodes.OK, reviewEvents, headers);
- });
-
- it(`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`, () =>
- testAction({
- action: actions.fetchStageData,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_STAGE_DATA' },
- { type: 'RECEIVE_STAGE_DATA_SUCCESS', payload: reviewEvents },
- { type: 'SET_PAGINATION', payload: { hasNextPage: true, page: 1 } },
- ],
- expectedActions: [],
- }));
-
- describe('with a successful request, but an error in the payload', () => {
- const tooMuchDataError = 'Too much data';
-
- beforeEach(() => {
- state = {
- ...defaultState,
- endpoints: mockEndpoints,
- selectedStage,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockStagePath).reply(httpStatusCodes.OK, { error: tooMuchDataError });
- });
-
- it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchStageData,
- state,
- payload: { error: tooMuchDataError },
- expectedMutations: [
- { type: 'REQUEST_STAGE_DATA' },
- { type: 'RECEIVE_STAGE_DATA_ERROR', payload: tooMuchDataError },
- ],
- expectedActions: [],
- }));
- });
-
- describe('with a failing request', () => {
- beforeEach(() => {
- state = {
- ...defaultState,
- endpoints: mockEndpoints,
- selectedStage,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockStagePath).reply(httpStatusCodes.BAD_REQUEST);
- });
-
- it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchStageData,
- state,
- payload: {},
- expectedMutations: [{ type: 'REQUEST_STAGE_DATA' }, { type: 'RECEIVE_STAGE_DATA_ERROR' }],
- expectedActions: [],
- }));
- });
- });
-
- describe('fetchValueStreams', () => {
- const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/;
-
- beforeEach(() => {
- state = {
- endpoints: mockEndpoints,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
- });
-
- it(`commits the 'REQUEST_VALUE_STREAMS' mutation`, () =>
- testAction({
- action: actions.fetchValueStreams,
- state,
- payload: {},
- expectedMutations: [{ type: 'REQUEST_VALUE_STREAMS' }],
- expectedActions: [{ type: 'receiveValueStreamsSuccess' }],
- }));
-
- describe('with a failing request', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
- });
-
- it(`commits the 'RECEIVE_VALUE_STREAMS_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchValueStreams,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_VALUE_STREAMS' },
- { type: 'RECEIVE_VALUE_STREAMS_ERROR', payload: httpStatusCodes.BAD_REQUEST },
- ],
- expectedActions: [],
- }));
- });
- });
-
- describe('receiveValueStreamsSuccess', () => {
- const mockValueStream = {
- id: 'mockDefault',
- name: 'mock default',
- };
- const mockValueStreams = [mockValueStream, selectedValueStream];
- it('with data, will set the first value stream', () => {
- testAction({
- action: actions.receiveValueStreamsSuccess,
- state,
- payload: mockValueStreams,
- expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: mockValueStreams }],
- expectedActions: [{ type: 'setSelectedValueStream', payload: mockValueStream }],
- });
- });
-
- it('without data, will set the default value stream', () => {
- testAction({
- action: actions.receiveValueStreamsSuccess,
- state,
- payload: [],
- expectedMutations: [{ type: 'RECEIVE_VALUE_STREAMS_SUCCESS', payload: [] }],
- expectedActions: [{ type: 'setSelectedValueStream', payload: selectedValueStream }],
- });
- });
- });
-
- describe('fetchValueStreamStages', () => {
- const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/;
-
- beforeEach(() => {
- state = {
- endpoints: mockEndpoints,
- selectedValueStream,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
- });
-
- it(`commits the 'REQUEST_VALUE_STREAM_STAGES' and 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' mutations`, () =>
- testAction({
- action: actions.fetchValueStreamStages,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_VALUE_STREAM_STAGES' },
- { type: 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS' },
- ],
- expectedActions: [],
- }));
-
- describe('with a failing request', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
- });
-
- it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchValueStreamStages,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_VALUE_STREAM_STAGES' },
- { type: 'RECEIVE_VALUE_STREAM_STAGES_ERROR', payload: httpStatusCodes.BAD_REQUEST },
- ],
- expectedActions: [],
- }));
- });
- });
-
- describe('fetchStageMedians', () => {
- const mockValueStreamPath = /median/;
-
- const stageMediansPayload = [
- { id: 'issue', value: null },
- { id: 'plan', value: null },
- { id: 'code', value: null },
- ];
-
- const stageMedianError = new Error(
- `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`,
- );
-
- beforeEach(() => {
- state = {
- fullPath: mockFullPath,
- selectedValueStream,
- stages: allowedStages,
- };
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.OK);
- });
-
- it(`commits the 'REQUEST_STAGE_MEDIANS' and 'RECEIVE_STAGE_MEDIANS_SUCCESS' mutations`, () =>
- testAction({
- action: actions.fetchStageMedians,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_STAGE_MEDIANS' },
- { type: 'RECEIVE_STAGE_MEDIANS_SUCCESS', payload: stageMediansPayload },
- ],
- expectedActions: [],
- }));
-
- describe('with a failing request', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
- });
-
- it(`commits the 'RECEIVE_VALUE_STREAM_STAGES_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchStageMedians,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_STAGE_MEDIANS' },
- { type: 'RECEIVE_STAGE_MEDIANS_ERROR', payload: stageMedianError },
- ],
- expectedActions: [],
- }));
- });
- });
-
- describe('fetchStageCountValues', () => {
- const mockValueStreamPath = /count/;
- const stageCountsPayload = [
- { id: 'issue', count: 1 },
- { id: 'plan', count: 2 },
- { id: 'code', count: 3 },
- ];
-
- const stageCountError = new Error(
- `Request failed with status code ${httpStatusCodes.BAD_REQUEST}`,
- );
-
- beforeEach(() => {
- state = {
- fullPath: mockFullPath,
- selectedValueStream,
- stages: allowedStages,
- };
- mock = new MockAdapter(axios);
- mock
- .onGet(mockValueStreamPath)
- .replyOnce(httpStatusCodes.OK, { count: 1 })
- .onGet(mockValueStreamPath)
- .replyOnce(httpStatusCodes.OK, { count: 2 })
- .onGet(mockValueStreamPath)
- .replyOnce(httpStatusCodes.OK, { count: 3 });
- });
-
- it(`commits the 'REQUEST_STAGE_COUNTS' and 'RECEIVE_STAGE_COUNTS_SUCCESS' mutations`, () =>
- testAction({
- action: actions.fetchStageCountValues,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_STAGE_COUNTS' },
- { type: 'RECEIVE_STAGE_COUNTS_SUCCESS', payload: stageCountsPayload },
- ],
- expectedActions: [],
- }));
-
- describe('with a failing request', () => {
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(mockValueStreamPath).reply(httpStatusCodes.BAD_REQUEST);
- });
-
- it(`commits the 'RECEIVE_STAGE_COUNTS_ERROR' mutation`, () =>
- testAction({
- action: actions.fetchStageCountValues,
- state,
- payload: {},
- expectedMutations: [
- { type: 'REQUEST_STAGE_COUNTS' },
- { type: 'RECEIVE_STAGE_COUNTS_ERROR', payload: stageCountError },
- ],
- expectedActions: [],
- }));
- });
- });
-
- describe('refetchStageData', () => {
- it('will commit SET_LOADING and dispatch fetchValueStreamStageData actions', () =>
- testAction({
- action: actions.refetchStageData,
- state,
- payload: {},
- expectedMutations: [
- { type: 'SET_LOADING', payload: true },
- { type: 'SET_LOADING', payload: false },
- ],
- expectedActions: [{ type: 'fetchValueStreamStageData' }],
- }));
- });
-
- describe('fetchValueStreamStageData', () => {
- it('will dispatch the fetchStageData, fetchStageMedians and fetchStageCountValues actions', () =>
- testAction({
- action: actions.fetchValueStreamStageData,
- state,
- payload: {},
- expectedMutations: [],
- expectedActions: [
- { type: 'fetchStageData' },
- { type: 'fetchStageMedians' },
- { type: 'fetchStageCountValues' },
- ],
- }));
- });
-});
diff --git a/spec/frontend/cycle_analytics/store/getters_spec.js b/spec/frontend/cycle_analytics/store/getters_spec.js
deleted file mode 100644
index c9208045a68..00000000000
--- a/spec/frontend/cycle_analytics/store/getters_spec.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import * as getters from '~/cycle_analytics/store/getters';
-
-import {
- allowedStages,
- stageMedians,
- transformedProjectStagePathData,
- selectedStage,
- stageCounts,
- basePaginationResult,
- initialPaginationState,
-} from '../mock_data';
-
-describe('Value stream analytics getters', () => {
- let state = {};
-
- describe('pathNavigationData', () => {
- it('returns the transformed data', () => {
- state = { stages: allowedStages, medians: stageMedians, selectedStage, stageCounts };
- expect(getters.pathNavigationData(state)).toEqual(transformedProjectStagePathData);
- });
- });
-
- describe('paginationParams', () => {
- beforeEach(() => {
- state = { pagination: initialPaginationState };
- });
-
- it('returns the `pagination` type', () => {
- expect(getters.paginationParams(state)).toEqual(basePaginationResult);
- });
-
- it('returns the `sort` type', () => {
- expect(getters.paginationParams(state)).toEqual(basePaginationResult);
- });
-
- it('with page=10, sets the `page` property', () => {
- const page = 10;
- state = { pagination: { ...initialPaginationState, page } };
- expect(getters.paginationParams(state)).toEqual({ ...basePaginationResult, page });
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/store/mutations_spec.js b/spec/frontend/cycle_analytics/store/mutations_spec.js
deleted file mode 100644
index 2e9e5d91471..00000000000
--- a/spec/frontend/cycle_analytics/store/mutations_spec.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import { useFakeDate } from 'helpers/fake_date';
-import * as types from '~/cycle_analytics/store/mutation_types';
-import mutations from '~/cycle_analytics/store/mutations';
-import {
- PAGINATION_SORT_FIELD_END_EVENT,
- PAGINATION_SORT_DIRECTION_DESC,
-} from '~/cycle_analytics/constants';
-import {
- selectedStage,
- rawIssueEvents,
- issueEvents,
- selectedValueStream,
- rawValueStreamStages,
- valueStreamStages,
- rawStageMedians,
- formattedStageMedians,
- rawStageCounts,
- stageCounts,
- initialPaginationState as pagination,
-} from '../mock_data';
-
-let state;
-const rawEvents = rawIssueEvents.events;
-const convertedEvents = issueEvents.events;
-const mockRequestPath = 'fake/request/path';
-const mockCreatedAfter = '2020-06-18';
-const mockCreatedBefore = '2020-07-18';
-
-describe('Project Value Stream Analytics mutations', () => {
- useFakeDate(2020, 6, 18);
-
- beforeEach(() => {
- state = { pagination };
- });
-
- afterEach(() => {
- state = null;
- });
-
- it.each`
- mutation | stateKey | value
- ${types.REQUEST_VALUE_STREAMS} | ${'valueStreams'} | ${[]}
- ${types.RECEIVE_VALUE_STREAMS_ERROR} | ${'valueStreams'} | ${[]}
- ${types.REQUEST_VALUE_STREAM_STAGES} | ${'stages'} | ${[]}
- ${types.RECEIVE_VALUE_STREAM_STAGES_ERROR} | ${'stages'} | ${[]}
- ${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true}
- ${types.REQUEST_STAGE_DATA} | ${'isEmptyStage'} | ${false}
- ${types.REQUEST_STAGE_DATA} | ${'selectedStageEvents'} | ${[]}
- ${types.RECEIVE_STAGE_DATA_SUCCESS} | ${'isLoadingStage'} | ${false}
- ${types.RECEIVE_STAGE_DATA_SUCCESS} | ${'selectedStageEvents'} | ${[]}
- ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
- ${types.RECEIVE_STAGE_DATA_ERROR} | ${'selectedStageEvents'} | ${[]}
- ${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
- ${types.REQUEST_STAGE_MEDIANS} | ${'medians'} | ${{}}
- ${types.RECEIVE_STAGE_MEDIANS_ERROR} | ${'medians'} | ${{}}
- ${types.REQUEST_STAGE_COUNTS} | ${'stageCounts'} | ${{}}
- ${types.RECEIVE_STAGE_COUNTS_ERROR} | ${'stageCounts'} | ${{}}
- ${types.SET_NO_ACCESS_ERROR} | ${'hasNoAccessError'} | ${true}
- `('$mutation will set $stateKey to $value', ({ mutation, stateKey, value }) => {
- mutations[mutation](state);
-
- expect(state).toMatchObject({ [stateKey]: value });
- });
-
- const mockSetDatePayload = { createdAfter: mockCreatedAfter, createdBefore: mockCreatedBefore };
- const mockInitialPayload = {
- endpoints: { requestPath: mockRequestPath },
- currentGroup: { title: 'cool-group' },
- id: 1337,
- ...mockSetDatePayload,
- };
- const mockInitializedObj = {
- endpoints: { requestPath: mockRequestPath },
- ...mockSetDatePayload,
- };
-
- it.each`
- mutation | stateKey | value
- ${types.INITIALIZE_VSA} | ${'endpoints'} | ${{ requestPath: mockRequestPath }}
- ${types.INITIALIZE_VSA} | ${'createdAfter'} | ${mockCreatedAfter}
- ${types.INITIALIZE_VSA} | ${'createdBefore'} | ${mockCreatedBefore}
- `('$mutation will set $stateKey', ({ mutation, stateKey, value }) => {
- mutations[mutation](state, { ...mockInitialPayload });
-
- expect(state).toMatchObject({ ...mockInitializedObj, [stateKey]: value });
- });
-
- it.each`
- mutation | payload | stateKey | value
- ${types.SET_DATE_RANGE} | ${mockSetDatePayload} | ${'createdAfter'} | ${mockCreatedAfter}
- ${types.SET_DATE_RANGE} | ${mockSetDatePayload} | ${'createdBefore'} | ${mockCreatedBefore}
- ${types.SET_LOADING} | ${true} | ${'isLoading'} | ${true}
- ${types.SET_LOADING} | ${false} | ${'isLoading'} | ${false}
- ${types.SET_SELECTED_VALUE_STREAM} | ${selectedValueStream} | ${'selectedValueStream'} | ${selectedValueStream}
- ${types.SET_PAGINATION} | ${pagination} | ${'pagination'} | ${{ ...pagination, sort: PAGINATION_SORT_FIELD_END_EVENT, direction: PAGINATION_SORT_DIRECTION_DESC }}
- ${types.SET_PAGINATION} | ${{ ...pagination, sort: 'duration', direction: 'asc' }} | ${'pagination'} | ${{ ...pagination, sort: 'duration', direction: 'asc' }}
- ${types.SET_SELECTED_STAGE} | ${selectedStage} | ${'selectedStage'} | ${selectedStage}
- ${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${[selectedValueStream]} | ${'valueStreams'} | ${[selectedValueStream]}
- ${types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS} | ${{ stages: rawValueStreamStages }} | ${'stages'} | ${valueStreamStages}
- ${types.RECEIVE_STAGE_MEDIANS_SUCCESS} | ${rawStageMedians} | ${'medians'} | ${formattedStageMedians}
- ${types.RECEIVE_STAGE_COUNTS_SUCCESS} | ${rawStageCounts} | ${'stageCounts'} | ${stageCounts}
- `(
- '$mutation with $payload will set $stateKey to $value',
- ({ mutation, payload, stateKey, value }) => {
- mutations[mutation](state, payload);
-
- expect(state).toMatchObject({ [stateKey]: value });
- },
- );
-
- describe('with a stage selected', () => {
- beforeEach(() => {
- state = {
- selectedStage,
- };
- });
-
- it.each`
- mutation | payload | stateKey | value
- ${types.RECEIVE_STAGE_DATA_SUCCESS} | ${[]} | ${'isEmptyStage'} | ${true}
- ${types.RECEIVE_STAGE_DATA_SUCCESS} | ${rawEvents} | ${'selectedStageEvents'} | ${convertedEvents}
- ${types.RECEIVE_STAGE_DATA_SUCCESS} | ${rawEvents} | ${'isEmptyStage'} | ${false}
- `(
- '$mutation with $payload will set $stateKey to $value',
- ({ mutation, payload, stateKey, value }) => {
- mutations[mutation](state, payload);
-
- expect(state).toMatchObject({ [stateKey]: value });
- },
- );
- });
-});
diff --git a/spec/frontend/cycle_analytics/total_time_spec.js b/spec/frontend/cycle_analytics/total_time_spec.js
deleted file mode 100644
index 8cf9feab6e9..00000000000
--- a/spec/frontend/cycle_analytics/total_time_spec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { mount } from '@vue/test-utils';
-import TotalTime from '~/cycle_analytics/components/total_time.vue';
-
-describe('TotalTime', () => {
- let wrapper = null;
-
- const createComponent = (propsData) => {
- return mount(TotalTime, {
- propsData,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('with a valid time object', () => {
- it.each`
- time
- ${{ seconds: 35 }}
- ${{ mins: 47, seconds: 3 }}
- ${{ days: 3, mins: 47, seconds: 3 }}
- ${{ hours: 23, mins: 10 }}
- ${{ hours: 7, mins: 20, seconds: 10 }}
- `('with $time', ({ time }) => {
- wrapper = createComponent({
- time,
- });
-
- expect(wrapper.html()).toMatchSnapshot();
- });
- });
-
- describe('with a blank object', () => {
- beforeEach(() => {
- wrapper = createComponent({
- time: {},
- });
- });
-
- it('should render --', () => {
- expect(wrapper.html()).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/utils_spec.js b/spec/frontend/cycle_analytics/utils_spec.js
deleted file mode 100644
index 51405a1ba4d..00000000000
--- a/spec/frontend/cycle_analytics/utils_spec.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import {
- transformStagesForPathNavigation,
- medianTimeToParsedSeconds,
- formatMedianValues,
- filterStagesByHiddenStatus,
- buildCycleAnalyticsInitialData,
-} from '~/cycle_analytics/utils';
-import {
- selectedStage,
- allowedStages,
- stageMedians,
- pathNavIssueMetric,
- rawStageMedians,
-} from './mock_data';
-
-describe('Value stream analytics utils', () => {
- describe('transformStagesForPathNavigation', () => {
- const stages = allowedStages;
- const response = transformStagesForPathNavigation({
- stages,
- medians: stageMedians,
- selectedStage,
- });
-
- describe('transforms the data as expected', () => {
- it('returns an array of stages', () => {
- expect(Array.isArray(response)).toBe(true);
- expect(response.length).toBe(stages.length);
- });
-
- it('selects the correct stage', () => {
- const selected = response.filter((stage) => stage.selected === true)[0];
-
- expect(selected.title).toBe(selectedStage.title);
- });
-
- it('includes the correct metric for the associated stage', () => {
- const issue = response.filter((stage) => stage.name === 'issue')[0];
-
- expect(issue.metric).toBe(pathNavIssueMetric);
- });
- });
- });
-
- describe('medianTimeToParsedSeconds', () => {
- it.each`
- value | result
- ${1036800} | ${'1w'}
- ${259200} | ${'3d'}
- ${172800} | ${'2d'}
- ${86400} | ${'1d'}
- ${1000} | ${'16m'}
- ${61} | ${'1m'}
- ${59} | ${'<1m'}
- ${0} | ${'-'}
- `('will correctly parse $value seconds into $result', ({ value, result }) => {
- expect(medianTimeToParsedSeconds(value)).toBe(result);
- });
- });
-
- describe('formatMedianValues', () => {
- const calculatedMedians = formatMedianValues(rawStageMedians);
-
- it('returns an object with each stage and their median formatted for display', () => {
- rawStageMedians.forEach(({ id, value }) => {
- expect(calculatedMedians).toMatchObject({ [id]: medianTimeToParsedSeconds(value) });
- });
- });
- });
-
- describe('filterStagesByHiddenStatus', () => {
- const hiddenStages = [{ title: 'three', hidden: true }];
- const visibleStages = [
- { title: 'one', hidden: false },
- { title: 'two', hidden: false },
- ];
- const mockStages = [...visibleStages, ...hiddenStages];
-
- it.each`
- isHidden | result
- ${false} | ${visibleStages}
- ${undefined} | ${hiddenStages}
- ${true} | ${hiddenStages}
- `('with isHidden=$isHidden returns matching stages', ({ isHidden, result }) => {
- expect(filterStagesByHiddenStatus(mockStages, isHidden)).toEqual(result);
- });
- });
-
- describe('buildCycleAnalyticsInitialData', () => {
- let res = null;
- const projectId = '5';
- const createdAfter = '2021-09-01';
- const createdBefore = '2021-11-06';
- const groupId = '146';
- const groupPath = 'fake-group';
- const fullPath = 'fake-group/fake-project';
- const labelsPath = '/fake-group/fake-project/-/labels.json';
- const milestonesPath = '/fake-group/fake-project/-/milestones.json';
- const requestPath = '/fake-group/fake-project/-/value_stream_analytics';
-
- const rawData = {
- projectId,
- createdBefore,
- createdAfter,
- fullPath,
- requestPath,
- labelsPath,
- milestonesPath,
- groupId,
- groupPath,
- };
-
- describe('with minimal data', () => {
- beforeEach(() => {
- res = buildCycleAnalyticsInitialData(rawData);
- });
-
- it('sets the projectId', () => {
- expect(res.projectId).toBe(parseInt(projectId, 10));
- });
-
- it('sets the date range', () => {
- expect(res.createdBefore).toEqual(new Date(createdBefore));
- expect(res.createdAfter).toEqual(new Date(createdAfter));
- });
-
- it('sets the endpoints', () => {
- const { endpoints } = res;
- expect(endpoints.fullPath).toBe(fullPath);
- expect(endpoints.requestPath).toBe(requestPath);
- expect(endpoints.labelsPath).toBe(labelsPath);
- expect(endpoints.milestonesPath).toBe(milestonesPath);
- expect(endpoints.groupId).toBe(parseInt(groupId, 10));
- expect(endpoints.groupPath).toBe(groupPath);
- });
-
- it('returns null when there is no stage', () => {
- expect(res.selectedStage).toBeNull();
- });
-
- it('returns false for missing features', () => {
- expect(res.features.cycleAnalyticsForGroups).toBe(false);
- });
- });
-
- describe('with a stage set', () => {
- const jsonStage = '{"id":"fakeStage","title":"fakeStage"}';
-
- it('parses the selectedStage data', () => {
- res = buildCycleAnalyticsInitialData({ ...rawData, stage: jsonStage });
-
- const { selectedStage: stage } = res;
-
- expect(stage.id).toBe('fakeStage');
- expect(stage.title).toBe('fakeStage');
- });
- });
-
- describe('with features set', () => {
- const fakeFeatures = { cycleAnalyticsForGroups: true };
-
- it('sets the feature flags', () => {
- res = buildCycleAnalyticsInitialData({
- ...rawData,
- gon: { licensed_features: fakeFeatures },
- });
- expect(res.features).toEqual(fakeFeatures);
- });
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/value_stream_filters_spec.js b/spec/frontend/cycle_analytics/value_stream_filters_spec.js
deleted file mode 100644
index 6e96a6d756a..00000000000
--- a/spec/frontend/cycle_analytics/value_stream_filters_spec.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import Daterange from '~/analytics/shared/components/daterange.vue';
-import ProjectsDropdownFilter from '~/analytics/shared/components/projects_dropdown_filter.vue';
-import FilterBar from '~/cycle_analytics/components/filter_bar.vue';
-import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
-import {
- createdAfter as startDate,
- createdBefore as endDate,
- currentGroup,
- selectedProjects,
-} from './mock_data';
-
-function createComponent(props = {}) {
- return shallowMount(ValueStreamFilters, {
- propsData: {
- selectedProjects,
- groupId: currentGroup.id,
- groupPath: currentGroup.fullPath,
- startDate,
- endDate,
- ...props,
- },
- });
-}
-
-describe('ValueStreamFilters', () => {
- let wrapper;
-
- const findProjectsDropdown = () => wrapper.findComponent(ProjectsDropdownFilter);
- const findDateRangePicker = () => wrapper.findComponent(Daterange);
- const findFilterBar = () => wrapper.findComponent(FilterBar);
-
- beforeEach(() => {
- wrapper = createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('will render the filter bar', () => {
- expect(findFilterBar().exists()).toBe(true);
- });
-
- it('will render the projects dropdown', () => {
- expect(findProjectsDropdown().exists()).toBe(true);
- expect(wrapper.findComponent(ProjectsDropdownFilter).props()).toEqual(
- expect.objectContaining({
- queryParams: wrapper.vm.projectsQueryParams,
- multiSelect: wrapper.vm.$options.multiProjectSelect,
- }),
- );
- });
-
- it('will render the date range picker', () => {
- expect(findDateRangePicker().exists()).toBe(true);
- });
-
- it('will emit `selectProject` when a project is selected', () => {
- findProjectsDropdown().vm.$emit('selected');
-
- expect(wrapper.emitted('selectProject')).not.toBeUndefined();
- });
-
- it('will emit `setDateRange` when the date range changes', () => {
- findDateRangePicker().vm.$emit('change');
-
- expect(wrapper.emitted('setDateRange')).not.toBeUndefined();
- });
-
- describe('hasDateRangeFilter = false', () => {
- beforeEach(() => {
- wrapper = createComponent({ hasDateRangeFilter: false });
- });
-
- it('will not render the date range picker', () => {
- expect(findDateRangePicker().exists()).toBe(false);
- });
- });
-
- describe('hasProjectFilter = false', () => {
- beforeEach(() => {
- wrapper = createComponent({ hasProjectFilter: false });
- });
-
- it('will not render the project dropdown', () => {
- expect(findProjectsDropdown().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
deleted file mode 100644
index 948dc5c9be2..00000000000
--- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import { GlSkeletonLoader } from '@gitlab/ui';
-import { nextTick } from 'vue';
-import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
-import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
-import { VSA_METRICS_GROUPS, METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
-import { prepareTimeMetricsData } from '~/analytics/shared/utils';
-import MetricTile from '~/analytics/shared/components/metric_tile.vue';
-import { createAlert } from '~/flash';
-import { group } from './mock_data';
-
-jest.mock('~/flash');
-
-describe('ValueStreamMetrics', () => {
- let wrapper;
- let mockGetValueStreamSummaryMetrics;
- let mockFilterFn;
-
- const { full_path: requestPath } = group;
- const fakeReqName = 'Mock metrics';
- const metricsRequestFactory = () => ({
- request: mockGetValueStreamSummaryMetrics,
- endpoint: METRIC_TYPE_SUMMARY,
- name: fakeReqName,
- });
-
- const createComponent = (props = {}) => {
- return shallowMountExtended(ValueStreamMetrics, {
- propsData: {
- requestPath,
- requestParams: {},
- requests: [metricsRequestFactory()],
- ...props,
- },
- });
- };
-
- const findMetrics = () => wrapper.findAllComponents(MetricTile);
- const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group');
-
- const expectToHaveRequest = (fields) => {
- expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
- endpoint: METRIC_TYPE_SUMMARY,
- requestPath,
- ...fields,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('with successful requests', () => {
- beforeEach(() => {
- mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
- });
-
- it('will display a loader with pending requests', async () => {
- wrapper = createComponent();
- await nextTick();
-
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
- });
-
- describe('with data loaded', () => {
- beforeEach(async () => {
- wrapper = createComponent();
- await waitForPromises();
- });
-
- it('fetches data from the value stream analytics endpoint', () => {
- expectToHaveRequest({ params: {} });
- });
-
- describe.each`
- index | identifier | value | label
- ${0} | ${metricsData[0].identifier} | ${metricsData[0].value} | ${metricsData[0].title}
- ${1} | ${metricsData[1].identifier} | ${metricsData[1].value} | ${metricsData[1].title}
- ${2} | ${metricsData[2].identifier} | ${metricsData[2].value} | ${metricsData[2].title}
- ${3} | ${metricsData[3].identifier} | ${metricsData[3].value} | ${metricsData[3].title}
- `('metric tiles', ({ identifier, index, value, label }) => {
- it(`renders a metric tile component for "${label}"`, () => {
- const metric = findMetrics().at(index);
- expect(metric.props('metric')).toMatchObject({ identifier, value, label });
- expect(metric.isVisible()).toBe(true);
- });
- });
-
- it('will not display a loading icon', () => {
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
- });
-
- describe('filterFn', () => {
- const transferredMetricsData = prepareTimeMetricsData(metricsData, METRICS_POPOVER_CONTENT);
-
- it('with a filter function, will call the function with the metrics data', async () => {
- const filteredData = [
- { identifier: 'issues', value: '3', title: 'New Issues', description: 'foo' },
- ];
- mockFilterFn = jest.fn(() => filteredData);
-
- wrapper = createComponent({
- filterFn: mockFilterFn,
- });
-
- await waitForPromises();
-
- expect(mockFilterFn).toHaveBeenCalledWith(transferredMetricsData);
- expect(wrapper.vm.metrics).toEqual(filteredData);
- });
-
- it('without a filter function, it will only update the metrics', async () => {
- wrapper = createComponent();
-
- await waitForPromises();
-
- expect(mockFilterFn).not.toHaveBeenCalled();
- expect(wrapper.vm.metrics).toEqual(transferredMetricsData);
- });
- });
-
- describe('with additional params', () => {
- beforeEach(async () => {
- wrapper = createComponent({
- requestParams: {
- 'project_ids[]': [1],
- created_after: '2020-01-01',
- created_before: '2020-02-01',
- },
- });
-
- await waitForPromises();
- });
-
- it('fetches data for the `getValueStreamSummaryMetrics` request', () => {
- expectToHaveRequest({
- params: {
- 'project_ids[]': [1],
- created_after: '2020-01-01',
- created_before: '2020-02-01',
- },
- });
- });
- });
-
- describe('groupBy', () => {
- beforeEach(async () => {
- mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
- wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS });
- await waitForPromises();
- });
-
- it('renders the metrics as separate groups', () => {
- const groups = findMetricsGroups();
- expect(groups).toHaveLength(VSA_METRICS_GROUPS.length);
- });
-
- it('renders titles for each group', () => {
- const groups = findMetricsGroups();
- groups.wrappers.forEach((g, index) => {
- const { title } = VSA_METRICS_GROUPS[index];
- expect(g.html()).toContain(title);
- });
- });
- });
- });
- });
-
- describe('with a request failing', () => {
- beforeEach(async () => {
- mockGetValueStreamSummaryMetrics = jest.fn().mockRejectedValue();
- wrapper = createComponent();
-
- await waitForPromises();
- });
-
- it('should render an error message', () => {
- expect(createAlert).toHaveBeenCalledWith({
- message: `There was an error while fetching value stream analytics ${fakeReqName} data.`,
- });
- });
- });
-});