Commit 87aabe9f authored by Frédéric Caplette's avatar Frédéric Caplette

Merge branch '327457-vsa-fe-deep-link-the-url-query-parameters' into 'master'

VSA - Deep link the url query parameters

See merge request gitlab-org/gitlab!72777
parents b343b0ef cd4c047c
......@@ -2,10 +2,12 @@
import { GlLoadingIcon } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mapActions, mapState, mapGetters } from 'vuex';
import { toYmd } from '~/analytics/shared/utils';
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 ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { __ } from '~/locale';
import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants';
......@@ -19,6 +21,7 @@ export default {
StageTable,
ValueStreamFilters,
ValueStreamMetrics,
UrlSync,
},
props: {
noDataSvgPath: {
......@@ -54,6 +57,9 @@ export default {
'pagination',
]),
...mapGetters(['pathNavigationData', 'filterParams']),
isLoaded() {
return !this.isLoading && !this.isLoadingStage;
},
displayStageEvents() {
const { selectedStageEvents, isLoadingStage, isEmptyStage } = this;
return selectedStageEvents.length && !isLoadingStage && !isEmptyStage;
......@@ -98,6 +104,16 @@ export default {
metricsRequests() {
return this.features?.cycleAnalyticsForGroups ? METRICS_REQUESTS : SUMMARY_METRICS_REQUEST;
},
query() {
return {
created_after: toYmd(this.createdAfter),
created_before: toYmd(this.createdBefore),
stage_id: this.selectedStage?.id || null,
sort: this.pagination?.sort || null,
direction: this.pagination?.direction || null,
page: this.pagination?.page || null,
};
},
},
methods: {
...mapActions([
......@@ -176,5 +192,6 @@ export default {
:pagination="pagination"
@handleUpdatePagination="onHandleUpdatePagination"
/>
<url-sync v-if="isLoaded" :query="query" />
</div>
</template>
......@@ -5,8 +5,6 @@ import {
} from '~/api/analytics_api';
import { __, s__ } from '~/locale';
export const DEFAULT_DAYS_IN_PAST = 30;
export const DEFAULT_DAYS_TO_DISPLAY = 30;
export const OVERVIEW_STAGE_ID = 'overview';
export const DEFAULT_VALUE_STREAM = {
......
import Vue from 'vue';
import {
extractFilterQueryParameters,
extractPaginationQueryParameters,
} from '~/analytics/shared/utils';
import Translate from '../vue_shared/translate';
import CycleAnalytics from './components/base.vue';
import { DEFAULT_DAYS_TO_DISPLAY } from './constants';
import createStore from './store';
import { calculateFormattedDayInPast } from './utils';
import { buildCycleAnalyticsInitialData } from './utils';
Vue.use(Translate);
export default () => {
const store = createStore();
const el = document.querySelector('#js-cycle-analytics');
const {
noAccessSvgPath,
noDataSvgPath,
requestPath,
fullPath,
projectId,
groupId,
groupPath,
labelsPath,
milestonesPath,
} = el.dataset;
const { noAccessSvgPath, noDataSvgPath } = el.dataset;
const initialData = buildCycleAnalyticsInitialData({ ...el.dataset, gon });
const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY);
const pagination = extractPaginationQueryParameters(window.location.search);
const {
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
selectedLabelList,
} = extractFilterQueryParameters(window.location.search);
store.dispatch('initializeVsa', {
projectId: parseInt(projectId, 10),
endpoints: {
requestPath,
fullPath,
labelsPath,
milestonesPath,
groupId: parseInt(groupId, 10),
groupPath,
},
features: {
cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups),
},
createdBefore: new Date(now),
createdAfter: new Date(past),
...initialData,
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
selectedLabelList,
pagination,
});
// eslint-disable-next-line no-new
......@@ -52,7 +44,6 @@ export default () => {
props: {
noDataSvgPath,
noAccessSvgPath,
fullPath,
},
}),
});
......
......@@ -14,7 +14,7 @@ import * as types from './mutation_types';
export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => {
commit(types.SET_SELECTED_VALUE_STREAM, valueStream);
return Promise.all([dispatch('fetchValueStreamStages'), dispatch('fetchCycleAnalyticsData')]);
return dispatch('fetchValueStreamStages');
};
export const fetchValueStreamStages = ({ commit, state }) => {
......@@ -46,10 +46,8 @@ export const fetchValueStreams = ({ commit, dispatch, state }) => {
} = state;
commit(types.REQUEST_VALUE_STREAMS);
const stageRequests = ['setSelectedStage', 'fetchStageMedians', 'fetchStageCountValues'];
return getProjectValueStreams(fullPath)
.then(({ data }) => dispatch('receiveValueStreamsSuccess', data))
.then(() => Promise.all(stageRequests.map((r) => dispatch(r))))
.catch(({ response: { status } }) => {
commit(types.RECEIVE_VALUE_STREAMS_ERROR, status);
});
......@@ -153,33 +151,36 @@ export const fetchStageCountValues = ({
});
};
export const setSelectedStage = ({ dispatch, commit, state: { stages } }, selectedStage = null) => {
const stage = selectedStage || stages[0];
commit(types.SET_SELECTED_STAGE, stage);
return dispatch('fetchStageData');
};
export const setLoading = ({ commit }, value) => commit(types.SET_LOADING, value);
const refetchStageData = (dispatch) => {
return Promise.resolve()
.then(() => dispatch('setLoading', true))
.then(() =>
export const fetchValueStreamStageData = ({ dispatch }) =>
Promise.all([
dispatch('fetchCycleAnalyticsData'),
dispatch('fetchStageData'),
dispatch('fetchStageMedians'),
dispatch('fetchStageCountValues'),
]),
)
.finally(() => dispatch('setLoading', false));
]);
export const refetchStageData = async ({ dispatch, commit }) => {
commit(types.SET_LOADING, true);
await dispatch('fetchValueStreamStageData');
commit(types.SET_LOADING, false);
};
export const setSelectedStage = ({ dispatch, commit }, selectedStage = null) => {
commit(types.SET_SELECTED_STAGE, selectedStage);
return dispatch('refetchStageData');
};
export const setFilters = ({ dispatch }) => refetchStageData(dispatch);
export const setFilters = ({ dispatch }) => dispatch('refetchStageData');
export const setDateRange = ({ dispatch, commit }, { createdAfter, createdBefore }) => {
commit(types.SET_DATE_RANGE, { createdAfter, createdBefore });
return refetchStageData(dispatch);
return dispatch('refetchStageData');
};
export const setInitialStage = ({ dispatch, commit, state: { stages } }, stage) => {
const selectedStage = stage || stages[0];
commit(types.SET_SELECTED_STAGE, selectedStage);
return dispatch('fetchValueStreamStageData');
};
export const updateStageTablePagination = (
......@@ -190,12 +191,18 @@ export const updateStageTablePagination = (
return dispatch('fetchStageData', selectedStage.id);
};
export const initializeVsa = ({ commit, dispatch }, initialData = {}) => {
export const initializeVsa = async ({ commit, dispatch }, initialData = {}) => {
commit(types.INITIALIZE_VSA, initialData);
const {
endpoints: { fullPath, groupPath, milestonesPath = '', labelsPath = '' },
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
selectedLabelList,
selectedStage = null,
} = initialData;
dispatch('filters/setEndpoints', {
labelsEndpoint: labelsPath,
milestonesEndpoint: milestonesPath,
......@@ -203,7 +210,15 @@ export const initializeVsa = ({ commit, dispatch }, initialData = {}) => {
projectEndpoint: fullPath,
});
return dispatch('setLoading', true)
.then(() => dispatch('fetchValueStreams'))
.finally(() => dispatch('setLoading', false));
dispatch('filters/initialize', {
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
selectedLabelList,
});
commit(types.SET_LOADING, true);
await dispatch('fetchValueStreams');
await dispatch('setInitialStage', selectedStage);
commit(types.SET_LOADING, false);
};
import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { hideFlash } from '~/flash';
import { getDateInPast } from '~/lib/utils/datetime/date_calculation_utility';
import { parseSeconds } from '~/lib/utils/datetime_utility';
import { formatTimeAsSummary } from '~/lib/utils/datetime/date_format_utility';
import { slugify } from '~/lib/utils/text_utility';
......@@ -74,23 +71,6 @@ export const formatMedianValues = (medians = []) =>
export const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
stages.filter(({ hidden = false }) => hidden === isHidden);
const toIsoFormat = (d) => dateFormat(d, dateFormats.isoDate);
/**
* Takes an integer specifying the number of days to subtract
* from the date specified will return the 2 dates, formatted as ISO dates
*
* @param {Number} daysInPast - Number of days in the past to subtract
* @param {Date} [today=new Date] - Date to subtract days from, defaults to today
* @returns {Object} Returns 'now' and the 'past' date formatted as ISO dates
*/
export const calculateFormattedDayInPast = (daysInPast, today = new Date()) => {
return {
now: toIsoFormat(today),
past: toIsoFormat(getDateInPast(today, daysInPast)),
};
};
/**
* @typedef {Object} MetricData
* @property {String} title - Title of the metric measured
......@@ -123,3 +103,43 @@ export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
description: popoverContent[key]?.description || '',
};
});
const extractFeatures = (gon) => ({
cycleAnalyticsForGroups: Boolean(gon?.licensed_features?.cycleAnalyticsForGroups),
});
/**
* Builds the initial data object for Value Stream Analytics with data loaded from the backend
*
* @param {Object} dataset - dataset object paseed to the frontend via data-* properties
* @returns {Object} - The initial data to load the app with
*/
export const buildCycleAnalyticsInitialData = ({
fullPath,
requestPath,
projectId,
groupId,
groupPath,
labelsPath,
milestonesPath,
stage,
createdAfter,
createdBefore,
gon,
} = {}) => {
return {
projectId: parseInt(projectId, 10),
endpoints: {
requestPath,
fullPath,
labelsPath,
milestonesPath,
groupId: parseInt(groupId, 10),
groupPath,
},
createdAfter: new Date(createdAfter),
createdBefore: new Date(createdBefore),
selectedStage: stage ? JSON.parse(stage) : null,
features: extractFeatures(gon),
};
};
......@@ -6,8 +6,10 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include CycleAnalyticsParams
include GracefulTimeoutHandling
include RedisTracking
extend ::Gitlab::Utils::Override
before_action :authorize_read_cycle_analytics!
before_action :load_value_stream, only: :show
track_redis_hll_event :show, name: 'p_analytics_valuestream'
......@@ -19,6 +21,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
def show
@cycle_analytics = Analytics::CycleAnalytics::ProjectLevel.new(project: @project, options: options(cycle_analytics_project_params))
@request_params ||= ::Gitlab::Analytics::CycleAnalytics::RequestParams.new(all_cycle_analytics_params)
respond_to do |format|
format.html do
......@@ -34,6 +37,15 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
private
override :all_cycle_analytics_params
def all_cycle_analytics_params
super.merge({ project: @project, value_stream: @value_stream })
end
def load_value_stream
@value_stream = Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(@project)
end
def cycle_analytics_json
{
summary: @cycle_analytics.summary,
......
- page_title _("Value Stream Analytics")
- data_attributes = @request_params.valid? ? @request_params.to_data_attributes : {}
- data_attributes.merge!(cycle_analytics_initial_data(@project, @group))
- add_page_specific_style 'page_bundles/cycle_analytics'
#js-cycle-analytics{ data: cycle_analytics_initial_data(@project, @group) }
#js-cycle-analytics{ data: data_attributes }
......@@ -6,6 +6,7 @@ RSpec.describe 'Value Stream Analytics', :js do
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:stage_table_selector) { '[data-testid="vsa-stage-table"]' }
let_it_be(:stage_filter_bar) { '[data-testid="vsa-filter-bar"]' }
let_it_be(:stage_table_event_selector) { '[data-testid="vsa-stage-event"]' }
let_it_be(:stage_table_event_title_selector) { '[data-testid="vsa-stage-event-title"]' }
let_it_be(:stage_table_pagination_selector) { '[data-testid="vsa-stage-pagination"]' }
......@@ -27,6 +28,9 @@ RSpec.describe 'Value Stream Analytics', :js do
def set_daterange(from_date, to_date)
page.find(".js-daterange-picker-from input").set(from_date)
page.find(".js-daterange-picker-to input").set(to_date)
# simulate a blur event
page.find(".js-daterange-picker-to input").send_keys(:tab)
wait_for_all_requests
end
......@@ -158,6 +162,18 @@ RSpec.describe 'Value Stream Analytics', :js do
expect(page).not_to have_text(original_first_title, exact: true)
end
it 'can navigate directly to a value stream stream stage with filters applied' do
visit project_cycle_analytics_path(project, created_before: '2019-12-31', created_after: '2019-11-01', stage_id: 'code', milestone_title: milestone.title)
wait_for_requests
expect(page).to have_selector('.gl-path-active-item-indigo', text: 'Code')
expect(page.find(".js-daterange-picker-from input").value).to eq("2019-11-01")
expect(page.find(".js-daterange-picker-to input").value).to eq("2019-12-31")
filter_bar = page.find(stage_filter_bar)
expect(filter_bar.find(".gl-filtered-search-token-data-content").text).to eq("%#{milestone.title}")
end
def stage_time_column
stage_table.find(stage_table_duration_column_header_selector).ancestor("th")
end
......
......@@ -9,7 +9,6 @@ import stagingStageFixtures from 'test_fixtures/projects/analytics/value_stream_
import { TEST_HOST } from 'helpers/test_constants';
import {
DEFAULT_VALUE_STREAM,
DEFAULT_DAYS_IN_PAST,
PAGINATION_TYPE,
PAGINATION_SORT_DIRECTION_DESC,
PAGINATION_SORT_FIELD_END_EVENT,
......@@ -17,6 +16,7 @@ import {
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);
......
......@@ -57,22 +57,12 @@ describe('Project Value Stream Analytics actions', () => {
const mutationTypes = (arr) => arr.map(({ type }) => type);
const mockFetchStageDataActions = [
{ type: 'setLoading', payload: true },
{ type: 'fetchCycleAnalyticsData' },
{ type: 'fetchStageData' },
{ type: 'fetchStageMedians' },
{ type: 'fetchStageCountValues' },
{ type: 'setLoading', payload: false },
];
describe.each`
action | payload | expectedActions | expectedMutations
${'setLoading'} | ${true} | ${[]} | ${[{ type: 'SET_LOADING', payload: true }]}
${'setDateRange'} | ${{ createdAfter, createdBefore }} | ${mockFetchStageDataActions} | ${[mockSetDateActionCommit]}
${'setFilters'} | ${[]} | ${mockFetchStageDataActions} | ${[]}
${'setSelectedStage'} | ${{ selectedStage }} | ${[{ type: 'fetchStageData' }]} | ${[{ type: 'SET_SELECTED_STAGE', payload: { selectedStage } }]}
${'setSelectedValueStream'} | ${{ selectedValueStream }} | ${[{ type: 'fetchValueStreamStages' }, { type: 'fetchCycleAnalyticsData' }]} | ${[{ type: 'SET_SELECTED_VALUE_STREAM', payload: { selectedValueStream } }]}
${'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}`, () =>
......@@ -86,9 +76,18 @@ describe('Project Value Stream Analytics actions', () => {
});
describe('initializeVsa', () => {
let mockDispatch;
let mockCommit;
const payload = { endpoints: mockEndpoints };
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,
......@@ -96,27 +95,63 @@ describe('Project Value Stream Analytics actions', () => {
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(() => {
mockDispatch = jest.fn(() => Promise.resolve());
mockCommit = jest.fn();
state = { ...state, stages: allowedStages };
});
it('will dispatch the setLoading and fetchValueStreams actions and commit INITIALIZE_VSA', async () => {
await actions.initializeVsa(
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: [
{
...state,
dispatch: mockDispatch,
commit: mockCommit,
type: 'SET_SELECTED_STAGE',
payload: fakeStage,
},
payload,
);
expect(mockCommit).toHaveBeenCalledWith('INITIALIZE_VSA', { endpoints: mockEndpoints });
],
expectedActions: [{ type: 'fetchValueStreamStageData' }],
});
});
});
expect(mockDispatch).toHaveBeenCalledTimes(4);
expect(mockDispatch).toHaveBeenCalledWith('filters/setEndpoints', mockFilterEndpoints);
expect(mockDispatch).toHaveBeenCalledWith('setLoading', true);
expect(mockDispatch).toHaveBeenCalledWith('fetchValueStreams');
expect(mockDispatch).toHaveBeenCalledWith('setLoading', false);
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' }],
});
});
});
});
......@@ -270,12 +305,7 @@ describe('Project Value Stream Analytics actions', () => {
state,
payload: {},
expectedMutations: [{ type: 'REQUEST_VALUE_STREAMS' }],
expectedActions: [
{ type: 'receiveValueStreamsSuccess' },
{ type: 'setSelectedStage' },
{ type: 'fetchStageMedians' },
{ type: 'fetchStageCountValues' },
],
expectedActions: [{ type: 'receiveValueStreamsSuccess' }],
}));
describe('with a failing request', () => {
......@@ -483,4 +513,34 @@ describe('Project Value Stream Analytics actions', () => {
}));
});
});
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 fetchCycleAnalyticsData, fetchStageData, fetchStageMedians and fetchStageCountValues actions', () =>
testAction({
action: actions.fetchValueStreamStageData,
state,
payload: {},
expectedMutations: [],
expectedActions: [
{ type: 'fetchCycleAnalyticsData' },
{ type: 'fetchStageData' },
{ type: 'fetchStageMedians' },
{ type: 'fetchStageCountValues' },
],
}));
});
});
......@@ -101,6 +101,7 @@ describe('Project Value Stream Analytics mutations', () => {
${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}
......
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import { useFakeDate } from 'helpers/fake_date';
import {
transformStagesForPathNavigation,
medianTimeToParsedSeconds,
formatMedianValues,
filterStagesByHiddenStatus,
calculateFormattedDayInPast,
prepareTimeMetricsData,
buildCycleAnalyticsInitialData,
} from '~/cycle_analytics/utils';
import { slugify } from '~/lib/utils/text_utility';
import {
......@@ -90,14 +89,6 @@ describe('Value stream analytics utils', () => {
});
});
describe('calculateFormattedDayInPast', () => {
useFakeDate(1815, 11, 10);
it('will return 2 dates, now and past', () => {
expect(calculateFormattedDayInPast(5)).toEqual({ now: '1815-12-10', past: '1815-12-05' });
});
});
describe('prepareTimeMetricsData', () => {
let prepared;
const [first, second] = metricsData;
......@@ -125,4 +116,87 @@ describe('Value stream analytics utils', () => {
]);
});
});
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);
});
});
});
});
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