Commit b4705bb2 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Add project vsa base specs

Adds base specs for the project level vsa
to test the rendered view

Migrate path navigation component to CE

Moves the VSA path navigation component from
EE to CE and updates tests

Updates the gitlab.pot file
parent 0fc6db6a
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { GlIcon, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { GlIcon, GlEmptyState, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigation.vue'; import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import banner from './banner.vue'; import banner from './banner.vue';
import stageCodeComponent from './stage_code_component.vue'; import stageCodeComponent from './stage_code_component.vue';
...@@ -132,7 +132,7 @@ export default { ...@@ -132,7 +132,7 @@ export default {
For now we can use the `withStageCounts` flag to ensure we don't display empty stage counts For now we can use the `withStageCounts` flag to ensure we don't display empty stage counts
Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/326705 Related issue: https://gitlab.com/gitlab-org/gitlab/-/issues/326705
--> -->
<div class="card"> <div class="card" data-testid="vsa-stage-overview-metrics">
<div class="card-header">{{ __('Recent Project Activity') }}</div> <div class="card-header">{{ __('Recent Project Activity') }}</div>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div v-for="item in summary" :key="item.title" class="gl-flex-grow-1 gl-text-center"> <div v-for="item in summary" :key="item.title" class="gl-flex-grow-1 gl-text-center">
...@@ -163,7 +163,7 @@ export default { ...@@ -163,7 +163,7 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<div class="stage-panel-container"> <div class="stage-panel-container" data-testid="vsa-stage-table">
<div class="card stage-panel gl-px-5"> <div class="card stage-panel gl-px-5">
<div class="card-header border-bottom-0"> <div class="card-header border-bottom-0">
<nav class="col-headers"> <nav class="col-headers">
......
export const DEFAULT_DAYS_TO_DISPLAY = 30; export const DEFAULT_DAYS_TO_DISPLAY = 30;
export const OVERVIEW_STAGE_ID = 'overview';
import { roundToNearestHalf, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { unescape } from 'lodash'; import { unescape } from 'lodash';
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
import { roundToNearestHalf, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { parseSeconds } from '~/lib/utils/datetime_utility'; import { parseSeconds } from '~/lib/utils/datetime_utility';
import { dasherize } from '~/lib/utils/text_utility'; import { dasherize } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '../locale'; import { __, s__, sprintf } from '../locale';
......
<script> <script>
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState } from '@gitlab/ui';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue';
import DateRange from '../../shared/components/daterange.vue'; import DateRange from '../../shared/components/daterange.vue';
import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue'; import ProjectsDropdownFilter from '../../shared/components/projects_dropdown_filter.vue';
import { DATE_RANGE_LIMIT } from '../../shared/constants'; import { DATE_RANGE_LIMIT } from '../../shared/constants';
import { toYmd } from '../../shared/utils'; import { toYmd } from '../../shared/utils';
import { PROJECTS_PER_PAGE, OVERVIEW_STAGE_ID } from '../constants'; import { PROJECTS_PER_PAGE } from '../constants';
import DurationChart from './duration_chart.vue'; import DurationChart from './duration_chart.vue';
import FilterBar from './filter_bar.vue'; import FilterBar from './filter_bar.vue';
import Metrics from './metrics.vue'; import Metrics from './metrics.vue';
import PathNavigation from './path_navigation.vue';
import StageTableNew from './stage_table_new.vue'; import StageTableNew from './stage_table_new.vue';
import TypeOfWorkCharts from './type_of_work_charts.vue'; import TypeOfWorkCharts from './type_of_work_charts.vue';
import ValueStreamSelect from './value_stream_select.vue'; import ValueStreamSelect from './value_stream_select.vue';
......
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
export const PROJECTS_PER_PAGE = 50; export const PROJECTS_PER_PAGE = 50;
...@@ -52,7 +53,6 @@ export const OVERVIEW_METRICS = { ...@@ -52,7 +53,6 @@ export const OVERVIEW_METRICS = {
export const FETCH_VALUE_STREAM_DATA = 'fetchValueStreamData'; export const FETCH_VALUE_STREAM_DATA = 'fetchValueStreamData';
export const OVERVIEW_STAGE_ID = 'overview';
export const OVERVIEW_STAGE_CONFIG = { export const OVERVIEW_STAGE_CONFIG = {
id: OVERVIEW_STAGE_ID, id: OVERVIEW_STAGE_ID,
slug: OVERVIEW_STAGE_ID, slug: OVERVIEW_STAGE_ID,
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { import {
filterStagesByHiddenStatus, filterStagesByHiddenStatus,
pathNavigationData as basePathNavigationData, pathNavigationData as basePathNavigationData,
...@@ -8,12 +9,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils'; ...@@ -8,12 +9,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants'; import { dateFormats } from '../../shared/constants';
import { import { DEFAULT_VALUE_STREAM_ID, OVERVIEW_STAGE_CONFIG, PAGINATION_TYPE } from '../constants';
DEFAULT_VALUE_STREAM_ID,
OVERVIEW_STAGE_CONFIG,
PAGINATION_TYPE,
OVERVIEW_STAGE_ID,
} from '../constants';
export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN; export const hasNoAccessError = (state) => state.errorCode === httpStatus.FORBIDDEN;
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils'; import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import createFlash, { hideFlash } from '~/flash'; import createFlash, { hideFlash } from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
...@@ -8,7 +9,6 @@ import httpStatus from '~/lib/utils/http_status'; ...@@ -8,7 +9,6 @@ import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase, slugify } from '~/lib/utils/text_utility'; import { convertToSnakeCase, slugify } from '~/lib/utils/text_utility';
import { dateFormats } from '../shared/constants'; import { dateFormats } from '../shared/constants';
import { toYmd } from '../shared/utils'; import { toYmd } from '../shared/utils';
import { OVERVIEW_STAGE_ID } from './constants';
const EVENT_TYPE_LABEL = 'label'; const EVENT_TYPE_LABEL = 'label';
......
...@@ -7,7 +7,6 @@ import Component from 'ee/analytics/cycle_analytics/components/base.vue'; ...@@ -7,7 +7,6 @@ import Component from 'ee/analytics/cycle_analytics/components/base.vue';
import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue'; import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue';
import FilterBar from 'ee/analytics/cycle_analytics/components/filter_bar.vue'; import FilterBar from 'ee/analytics/cycle_analytics/components/filter_bar.vue';
import Metrics from 'ee/analytics/cycle_analytics/components/metrics.vue'; import Metrics from 'ee/analytics/cycle_analytics/components/metrics.vue';
import PathNavigation from 'ee/analytics/cycle_analytics/components/path_navigation.vue';
import StageTableNew from 'ee/analytics/cycle_analytics/components/stage_table_new.vue'; import StageTableNew from 'ee/analytics/cycle_analytics/components/stage_table_new.vue';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
...@@ -20,6 +19,7 @@ import Daterange from 'ee/analytics/shared/components/daterange.vue'; ...@@ -20,6 +19,7 @@ import Daterange from 'ee/analytics/shared/components/daterange.vue';
import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue'; import ProjectsDropdownFilter from 'ee/analytics/shared/components/projects_dropdown_filter.vue';
import { toYmd } from 'ee/analytics/shared/utils'; import { toYmd } from 'ee/analytics/shared/utils';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
...@@ -101,7 +101,7 @@ async function shouldMergeUrlParams(wrapper, result) { ...@@ -101,7 +101,7 @@ async function shouldMergeUrlParams(wrapper, result) {
expect(commonUtils.historyPushState).toHaveBeenCalled(); expect(commonUtils.historyPushState).toHaveBeenCalled();
} }
describe('Value Stream Analytics component', () => { describe('EE Value Stream Analytics component', () => {
let wrapper; let wrapper;
let mock; let mock;
let store; let store;
......
import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Component from 'ee/analytics/cycle_analytics/components/path_navigation.vue'; import Component from '~/cycle_analytics/components/path_navigation.vue';
import { transformedStagePathData, issueStage } from '../mock_data'; import { transformedStagePathData, issueStage } from '../mock_data';
describe('PathNavigation', () => { describe('Group PathNavigation', () => {
let wrapper = null; let wrapper = null;
const createComponent = (props) => { const createComponent = (props) => {
...@@ -17,18 +16,10 @@ describe('PathNavigation', () => { ...@@ -17,18 +16,10 @@ describe('PathNavigation', () => {
}); });
}; };
const pathNavigationTitles = () => {
return wrapper.findAll('.gl-path-button');
};
const pathNavigationItems = () => { const pathNavigationItems = () => {
return wrapper.findAll('.gl-path-nav-list-item'); return wrapper.findAll('.gl-path-nav-list-item');
}; };
const clickItemAt = (index) => {
pathNavigationTitles().at(index).trigger('click');
};
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
}); });
...@@ -38,100 +29,39 @@ describe('PathNavigation', () => { ...@@ -38,100 +29,39 @@ describe('PathNavigation', () => {
wrapper = null; wrapper = null;
}); });
describe('displays correctly', () => { describe('popovers', () => {
it('has the correct props', () => { beforeEach(() => {
expect(wrapper.find(GlPath).props('items')).toMatchObject(transformedStagePathData); wrapper = createComponent({ stages: transformedStagePathData });
}); });
it('contains all the expected stages', () => { it('renders popovers for all stages except for the overview stage', () => {
const html = wrapper.find(GlPath).html(); const pathItemContent = pathNavigationItems().wrappers;
const [overviewStage, ...popoverStages] = pathItemContent;
transformedStagePathData.forEach((stage) => {
expect(html).toContain(stage.title);
});
});
describe('loading', () => {
describe('is false', () => {
it('displays the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(true);
});
it('hides the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
});
// TODO: make this test more granular
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe('popovers', () => {
beforeEach(() => {
wrapper = createComponent({ stages: transformedStagePathData });
});
it('renders popovers for all stages except for the overview stage', () => { expect(overviewStage.text()).toContain('Overview');
const pathItemContent = pathNavigationItems().wrappers; expect(overviewStage.find('[data-testid="stage-item-popover"]').exists()).toBe(false);
const [overviewStage, ...popoverStages] = pathItemContent;
expect(overviewStage.text()).toContain('Overview'); popoverStages.forEach((stage) => {
expect(overviewStage.find('[data-testid="stage-item-popover"]').exists()).toBe(false); expect(stage.find('[data-testid="stage-item-popover"]').exists()).toBe(true);
popoverStages.forEach((stage) => {
expect(stage.find('[data-testid="stage-item-popover"]').exists()).toBe(true);
});
});
it('shows the sanitized start event description for the first stage item', () => {
const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
const expectedStartEventDescription = 'Issue created';
expect(firstPopover.text()).toContain(expectedStartEventDescription);
});
it('shows the sanitized end event description for the first stage item', () => {
const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
const expectedStartEventDescription =
'Issue first associated with a milestone or issue first added to a board';
expect(firstPopover.text()).toContain(expectedStartEventDescription);
});
it('shows the median stage time for the first stage item', () => {
const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
expect(firstPopover.text()).toContain('Stage time (median)');
});
});
});
describe('is true', () => {
beforeEach(() => {
wrapper = createComponent({ loading: true });
});
it('hides the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(false);
});
it('displays the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
});
}); });
}); });
});
describe('event handling', () => { it('shows the sanitized start event description for the first stage item', () => {
it('emits the selected event', () => { const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
expect(wrapper.emitted('selected')).toBeUndefined(); const expectedStartEventDescription = 'Issue created';
expect(firstPopover.text()).toContain(expectedStartEventDescription);
});
clickItemAt(0); it('shows the sanitized end event description for the first stage item', () => {
clickItemAt(1); const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
clickItemAt(2); const expectedStartEventDescription =
'Issue first associated with a milestone or issue first added to a board';
expect(firstPopover.text()).toContain(expectedStartEventDescription);
});
expect(wrapper.emitted().selected).toEqual([ it('shows the median stage time for the first stage item', () => {
[transformedStagePathData[0]], const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
[transformedStagePathData[1]], expect(firstPopover.text()).toContain('Stage time (median)');
[transformedStagePathData[2]],
]);
}); });
}); });
}); });
...@@ -529,10 +529,7 @@ describe('Value Stream Analytics actions', () => { ...@@ -529,10 +529,7 @@ describe('Value Stream Analytics actions', () => {
...state, ...state,
stages, stages,
currentGroup, currentGroup,
featureFlags: { featureFlags: state.featureFlags,
...state.featureFlags,
hasPathNavigation: true,
},
}; };
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onGet(endpoints.stageCount).reply(httpStatusCodes.OK, { events: [] }); mock.onGet(endpoints.stageCount).reply(httpStatusCodes.OK, { events: [] });
......
...@@ -13,12 +13,12 @@ import { ...@@ -13,12 +13,12 @@ import {
endDate, endDate,
allowedStages, allowedStages,
selectedProjects, selectedProjects,
transformedStagePathData,
issueStage, issueStage,
stageMedians, stageMedians,
stageCounts, stageCounts,
basePaginationResult, basePaginationResult,
initialPaginationState, initialPaginationState,
transformedStagePathData,
} from '../mock_data'; } from '../mock_data';
let state = null; let state = null;
......
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { OVERVIEW_STAGE_ID } from 'ee/analytics/cycle_analytics/constants';
import { import {
isStartEvent, isStartEvent,
isLabelEvent, isLabelEvent,
...@@ -21,6 +20,7 @@ import { ...@@ -21,6 +20,7 @@ import {
} from 'ee/analytics/cycle_analytics/utils'; } from 'ee/analytics/cycle_analytics/utils';
import { toYmd } from 'ee/analytics/shared/utils'; import { toYmd } from 'ee/analytics/shared/utils';
import { rawStageMedians } from 'jest/cycle_analytics/mock_data'; import { rawStageMedians } from 'jest/cycle_analytics/mock_data';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils'; import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import { getDatesInRange } from '~/lib/utils/datetime_utility'; import { getDatesInRange } from '~/lib/utils/datetime_utility';
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
......
...@@ -20460,9 +20460,6 @@ msgstr "" ...@@ -20460,9 +20460,6 @@ msgstr ""
msgid "Measured in bytes of code. Excludes generated and vendored code." msgid "Measured in bytes of code. Excludes generated and vendored code."
msgstr "" msgstr ""
msgid "Median"
msgstr ""
msgid "Medium Timeout Period" msgid "Medium Timeout Period"
msgstr "" msgstr ""
...@@ -25668,9 +25665,6 @@ msgstr "" ...@@ -25668,9 +25665,6 @@ msgstr ""
msgid "ProjectLastActivity|Never" msgid "ProjectLastActivity|Never"
msgstr "" msgstr ""
msgid "ProjectLifecycle|Stage"
msgstr ""
msgid "ProjectOverview|Fork" msgid "ProjectOverview|Fork"
msgstr "" msgstr ""
...@@ -32563,9 +32557,6 @@ msgstr "" ...@@ -32563,9 +32557,6 @@ msgstr ""
msgid "The password for your GitLab account on %{link_to_gitlab} has successfully been changed." msgid "The password for your GitLab account on %{link_to_gitlab} has successfully been changed."
msgstr "" msgstr ""
msgid "The phase of the development lifecycle."
msgstr ""
msgid "The pipeline has been deleted" msgid "The pipeline has been deleted"
msgstr "" msgstr ""
...@@ -32710,9 +32701,6 @@ msgstr "" ...@@ -32710,9 +32701,6 @@ msgstr ""
msgid "The username for the Jenkins server." msgid "The username for the Jenkins server."
msgstr "" msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
msgid "The value of the provided variable exceeds the %{count} character limit" msgid "The value of the provided variable exceeds the %{count} character limit"
msgstr "" msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PathNavigation displays correctly loading is false matches the snapshot 1`] = ` exports[`Project PathNavigation displays correctly loading is false matches the snapshot 1`] = `
<div <div
class="gl-path-nav" class="gl-path-nav"
data-testid="gl-path-nav" data-testid="gl-path-nav"
...@@ -33,29 +33,6 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -33,29 +33,6 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<li <li
class="gl-path-nav-list-item" class="gl-path-nav-list-item"
id="path-6-item-0" id="path-6-item-0"
>
<button
class="gl-path-button"
>
<svg
aria-hidden="true"
class="gl-mr-2 gl-icon s16"
data-testid="gl-path-item-icon"
role="img"
>
<use
href="#home"
/>
</svg>
Overview
<!---->
</button>
</li>
<li
class="gl-path-nav-list-item"
id="path-6-item-1"
> >
<button <button
class="gl-path-button gl-path-active-item-indigo" class="gl-path-button gl-path-active-item-indigo"
...@@ -63,7 +40,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -63,7 +40,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<!----> <!---->
Issue Issue
<!----> <span
class="gl-font-weight-normal gl-pl-2"
>
172800
</span>
</button> </button>
<div <div
...@@ -88,7 +69,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -88,7 +69,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
172800
</div> </div>
</div> </div>
</div> </div>
...@@ -110,7 +91,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -110,7 +91,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
172800 items -
</div> </div>
</div> </div>
</div> </div>
...@@ -118,58 +99,16 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -118,58 +99,16 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50" class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50"
> >
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 gl-pb-4 metric-label"
>
Start
</div>
<div
class="gl-display-flex gl-flex-direction-column gl-pb-4 stage-event-description"
>
<p
data-sourcepos="1:1-1:13"
dir="auto"
>
Issue created
</p>
</div>
</div>
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 metric-label"
>
Stop
</div>
<div
class="gl-display-flex gl-flex-direction-column stage-event-description"
>
<p
data-sourcepos="1:1-1:71"
dir="auto"
>
Issue first associated with a milestone or issue first added to a board
</p>
</div>
</div>
</div> </div>
Issue Issue
</div> </div>
</li> </li>
<li <li
class="gl-path-nav-list-item" class="gl-path-nav-list-item"
id="path-6-item-2" id="path-6-item-1"
> >
<button <button
class="gl-path-button" class="gl-path-button"
...@@ -177,7 +116,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -177,7 +116,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<!----> <!---->
Plan Plan
<!----> <span
class="gl-font-weight-normal gl-pl-2"
>
86400
</span>
</button> </button>
<div <div
...@@ -202,7 +145,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -202,7 +145,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
86400
</div> </div>
</div> </div>
</div> </div>
...@@ -224,7 +167,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -224,7 +167,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
86400 items -
</div> </div>
</div> </div>
</div> </div>
...@@ -232,58 +175,16 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -232,58 +175,16 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50" class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50"
> >
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 gl-pb-4 metric-label"
>
Start
</div>
<div
class="gl-display-flex gl-flex-direction-column gl-pb-4 stage-event-description"
>
<p
data-sourcepos="1:1-1:71"
dir="auto"
>
Issue first associated with a milestone or issue first added to a board
</p>
</div>
</div>
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 metric-label"
>
Stop
</div>
<div
class="gl-display-flex gl-flex-direction-column stage-event-description"
>
<p
data-sourcepos="1:1-1:33"
dir="auto"
>
Issue first mentioned in a commit
</p>
</div>
</div>
</div> </div>
Plan Plan
</div> </div>
</li> </li>
<li <li
class="gl-path-nav-list-item" class="gl-path-nav-list-item"
id="path-6-item-3" id="path-6-item-2"
> >
<button <button
class="gl-path-button" class="gl-path-button"
...@@ -291,7 +192,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -291,7 +192,11 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<!----> <!---->
Code Code
<!----> <span
class="gl-font-weight-normal gl-pl-2"
>
129600
</span>
</button> </button>
<div <div
...@@ -316,7 +221,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -316,7 +221,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
129600
</div> </div>
</div> </div>
</div> </div>
...@@ -338,7 +243,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -338,7 +243,7 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-pb-4 gl-font-weight-bold" class="gl-pb-4 gl-font-weight-bold"
> >
129600 items -
</div> </div>
</div> </div>
</div> </div>
...@@ -346,51 +251,9 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot ...@@ -346,51 +251,9 @@ exports[`PathNavigation displays correctly loading is false matches the snapshot
<div <div
class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50" class="gl-px-4 gl-pt-4 gl-border-t-1 gl-border-t-solid gl-border-gray-50"
> >
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 gl-pb-4 metric-label"
>
Start
</div>
<div
class="gl-display-flex gl-flex-direction-column gl-pb-4 stage-event-description"
>
<p
data-sourcepos="1:1-1:33"
dir="auto"
>
Issue first mentioned in a commit
</p>
</div>
</div>
<div <!---->
class="gl-display-flex gl-flex-direction-row"
>
<div
class="gl-display-flex gl-flex-direction-column gl-pr-4 metric-label"
>
Stop
</div>
<div
class="gl-display-flex gl-flex-direction-column stage-event-description"
>
<p
data-sourcepos="1:1-1:21"
dir="auto"
>
Merge request created
</p>
</div>
</div>
</div> </div>
Code Code
</div> </div>
......
describe.skip(() => {}); import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Component from '~/cycle_analytics/components/base.vue';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import createStore from '~/cycle_analytics/store';
const noDataSvgPath = 'path/to/no/data';
const noAccessSvgPath = 'path/to/no/access';
const localVue = createLocalVue();
localVue.use(Vuex);
let wrapper;
function createComponent() {
const store = createStore();
return extendedWrapper(
shallowMount(Component, {
localVue,
store,
propsData: {
noDataSvgPath,
noAccessSvgPath,
},
}),
);
}
const findPathNavigation = () => wrapper.findComponent(PathNavigation);
const findOverviewMetrics = () => wrapper.findByTestId('vsa-stage-overview-metrics');
const findStageTable = () => wrapper.findByTestId('vsa-stage-table');
describe('Value stream analytics component', () => {
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders the path navigation component', () => {
expect(findPathNavigation().exists()).toBe(true);
});
it('renders the overview metrics', () => {
expect(findOverviewMetrics().exists()).toBe(true);
});
it('renders the stage table', () => {
expect(findStageTable().exists()).toBe(true);
});
});
...@@ -43,7 +43,7 @@ const planStage = { ...@@ -43,7 +43,7 @@ const planStage = {
name: 'plan', name: 'plan',
legend: '', legend: '',
description: 'Time before an issue starts implementation', description: 'Time before an issue starts implementation',
value: 'about 21 hours', value: 75600,
}; };
const codeStage = { const codeStage = {
...@@ -52,7 +52,7 @@ const codeStage = { ...@@ -52,7 +52,7 @@ const codeStage = {
name: 'code', name: 'code',
legend: '', legend: '',
description: 'Time until first merge request', description: 'Time until first merge request',
value: '2 days', value: 172800,
}; };
const testStage = { const testStage = {
...@@ -61,7 +61,7 @@ const testStage = { ...@@ -61,7 +61,7 @@ const testStage = {
name: 'test', name: 'test',
legend: '', legend: '',
description: 'Total test time for all commits/merges', description: 'Total test time for all commits/merges',
value: 'about 5 hours', value: 17550,
}; };
const reviewStage = { const reviewStage = {
...@@ -79,7 +79,7 @@ const stagingStage = { ...@@ -79,7 +79,7 @@ const stagingStage = {
name: 'staging', name: 'staging',
legend: '', legend: '',
description: 'From merge request merge until deploy to production', description: 'From merge request merge until deploy to production',
value: '2 days', value: 172800,
}; };
export const selectedStage = { export const selectedStage = {
...@@ -91,7 +91,6 @@ export const selectedStage = { ...@@ -91,7 +91,6 @@ export const selectedStage = {
'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.', '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.',
component: 'stage-issue-component', component: 'stage-issue-component',
slug: 'issue', slug: 'issue',
id: 'issue',
}; };
export const stats = [issueStage, planStage, codeStage, testStage, reviewStage, stagingStage]; export const stats = [issueStage, planStage, codeStage, testStage, reviewStage, stagingStage];
......
import { GlPath, GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Component from '~/cycle_analytics/components/path_navigation.vue';
import { transformedProjectStagePathData, selectedStage } from './mock_data';
describe('Project PathNavigation', () => {
let wrapper = null;
const createComponent = (props) => {
return mount(Component, {
propsData: {
stages: transformedProjectStagePathData,
selectedStage,
loading: false,
...props,
},
});
};
const pathNavigationTitles = () => {
return wrapper.findAll('.gl-path-button');
};
const pathNavigationItems = () => {
return wrapper.findAll('.gl-path-nav-list-item');
};
const clickItemAt = (index) => {
pathNavigationTitles().at(index).trigger('click');
};
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('displays correctly', () => {
it('has the correct props', () => {
expect(wrapper.find(GlPath).props('items')).toMatchObject(transformedProjectStagePathData);
});
it('contains all the expected stages', () => {
const html = wrapper.find(GlPath).html();
transformedProjectStagePathData.forEach((stage) => {
expect(html).toContain(stage.title);
});
});
describe('loading', () => {
describe('is false', () => {
it('displays the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(true);
});
it('hides the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
});
// TODO: make this test more granular
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
describe('popovers', () => {
beforeEach(() => {
wrapper = createComponent({ stages: transformedProjectStagePathData });
});
it('renders popovers for all stages', () => {
const pathItemContent = pathNavigationItems().wrappers;
pathItemContent.forEach((stage) => {
expect(stage.find('[data-testid="stage-item-popover"]').exists()).toBe(true);
});
});
it('shows the median stage time for the first stage item', () => {
const firstPopover = wrapper.findAll('[data-testid="stage-item-popover"]').at(0);
expect(firstPopover.text()).toContain('Stage time (median)');
});
});
});
describe('is true', () => {
beforeEach(() => {
wrapper = createComponent({ loading: true });
});
it('hides the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(false);
});
it('displays the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoading).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]],
]);
});
});
});
...@@ -6,7 +6,6 @@ import { ...@@ -6,7 +6,6 @@ import {
selectedStage, selectedStage,
} from '../mock_data'; } from '../mock_data';
// TODO: move path navigation component to CE ee/spec/frontend/analytics/cycle_analytics/components/path_navigation_spec.js
describe('Value stream analytics getters', () => { describe('Value stream analytics getters', () => {
describe('pathNavigationData', () => { describe('pathNavigationData', () => {
it('returns the transformed data', () => { it('returns the transformed data', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment