Commit 9ed55b6d authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '326701-move-vsa-filters-to-ce' into 'master'

Migrate group VSA filters to project level

See merge request gitlab-org/gitlab!67340
parents 409f9aa6 0bbcd7f0
import dateFormat from 'dateformat';
import { dateFormats } from './constants';
export const filterBySearchTerm = (data = [], searchTerm = '', filterByKey = 'name') => {
if (!searchTerm?.length) return data;
return data.filter((item) => item[filterByKey].toLowerCase().includes(searchTerm.toLowerCase()));
};
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
<script>
import { GlIcon, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { GlLoadingIcon } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mapActions, mapState, mapGetters } from 'vuex';
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 { __ } from '~/locale';
import { SUMMARY_METRICS_REQUEST, METRICS_REQUESTS } from '../constants';
......@@ -13,11 +14,10 @@ const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
export default {
name: 'CycleAnalytics',
components: {
GlIcon,
GlLoadingIcon,
GlSprintf,
PathNavigation,
StageTable,
ValueStreamFilters,
ValueStreamMetrics,
},
props: {
......@@ -45,11 +45,12 @@ export default {
'selectedStageError',
'stages',
'summary',
'daysInPast',
'permissions',
'stageCounts',
'endpoints',
'features',
'createdBefore',
'createdAfter',
]),
...mapGetters(['pathNavigationData', 'filterParams']),
displayStageEvents() {
......@@ -99,8 +100,11 @@ export default {
},
methods: {
...mapActions(['fetchStageData', 'setSelectedStage', 'setDateRange']),
handleDateSelect(daysInPast) {
this.setDateRange(daysInPast);
onSetDateRange({ startDate, endDate }) {
this.setDateRange({
createdAfter: new Date(startDate),
createdBefore: new Date(endDate),
});
},
onSelectStage(stage) {
this.setSelectedStage(stage);
......@@ -134,29 +138,15 @@ export default {
:selected-stage="selectedStage"
@selected="onSelectStage"
/>
<div class="gl-flex-grow gl-align-self-end">
<div class="js-ca-dropdown dropdown inline">
<!-- eslint-disable-next-line @gitlab/vue-no-data-toggle -->
<button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
<span class="dropdown-label">
<gl-sprintf :message="$options.i18n.dropdownText">
<template #days>{{ daysInPast }}</template>
</gl-sprintf>
<gl-icon name="chevron-down" class="dropdown-menu-toggle-icon gl-top-3" />
</span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li v-for="days in $options.dayRangeOptions" :key="`day-range-${days}`">
<a href="#" @click.prevent="handleDateSelect(days)">
<gl-sprintf :message="$options.i18n.dropdownText">
<template #days>{{ days }}</template>
</gl-sprintf>
</a>
</li>
</ul>
</div>
</div>
</div>
<value-stream-filters
:group-id="endpoints.groupId"
:group-path="endpoints.groupPath"
:has-project-filter="false"
:start-date="createdAfter"
:end-date="createdBefore"
@setDateRange="onSetDateRange"
/>
<value-stream-metrics
:request-path="endpoints.fullPath"
:request-params="filterParams"
......
......@@ -68,26 +68,30 @@ export default {
v-if="hasDateRangeFilter || hasProjectFilter"
class="gl-display-flex gl-flex-direction-column gl-lg-flex-direction-row gl-justify-content-space-between"
>
<projects-dropdown-filter
v-if="hasProjectFilter"
:key="groupId"
class="js-projects-dropdown-filter project-select gl-mb-2 gl-lg-mb-0"
:group-id="groupId"
:group-namespace="groupPath"
:query-params="projectsQueryParams"
:multi-select="$options.multiProjectSelect"
:default-projects="selectedProjects"
@selected="$emit('selectProject', $event)"
/>
<date-range
v-if="hasDateRangeFilter"
:start-date="startDate"
:end-date="endDate"
:max-date-range="$options.maxDateRange"
:include-selected-date="true"
class="js-daterange-picker"
@change="$emit('setDateRange', $event)"
/>
<div>
<projects-dropdown-filter
v-if="hasProjectFilter"
:key="groupId"
class="js-projects-dropdown-filter project-select gl-mb-2 gl-lg-mb-0"
:group-id="groupId"
:group-namespace="groupPath"
:query-params="projectsQueryParams"
:multi-select="$options.multiProjectSelect"
:default-projects="selectedProjects"
@selected="$emit('selectProject', $event)"
/>
</div>
<div>
<date-range
v-if="hasDateRangeFilter"
:start-date="startDate"
:end-date="endDate"
:max-date-range="$options.maxDateRange"
:include-selected-date="true"
class="js-daterange-picker"
@change="$emit('setDateRange', $event)"
/>
</div>
</div>
</div>
</template>
import Vue from 'vue';
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';
Vue.use(Translate);
......@@ -14,19 +16,29 @@ export default () => {
requestPath,
fullPath,
projectId,
groupId,
groupPath,
labelsPath,
milestonesPath,
} = el.dataset;
const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY);
store.dispatch('initializeVsa', {
projectId: parseInt(projectId, 10),
groupPath,
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),
});
// eslint-disable-next-line no-new
......
......@@ -9,6 +9,7 @@ import {
import createFlash from '~/flash';
import { __ } from '~/locale';
import { DEFAULT_VALUE_STREAM, I18N_VSA_ERROR_STAGE_MEDIAN } from '../constants';
import { appendExtension } from '../utils';
import * as types from './mutation_types';
export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => {
......@@ -163,6 +164,7 @@ const refetchStageData = (dispatch) => {
dispatch('fetchCycleAnalyticsData'),
dispatch('fetchStageData'),
dispatch('fetchStageMedians'),
dispatch('fetchStageCountValues'),
]),
)
.finally(() => dispatch('setLoading', false));
......@@ -170,14 +172,24 @@ const refetchStageData = (dispatch) => {
export const setFilters = ({ dispatch }) => refetchStageData(dispatch);
export const setDateRange = ({ dispatch, commit }, daysInPast) => {
commit(types.SET_DATE_RANGE, daysInPast);
export const setDateRange = ({ dispatch, commit }, { createdAfter, createdBefore }) => {
commit(types.SET_DATE_RANGE, { createdAfter, createdBefore });
return refetchStageData(dispatch);
};
export const initializeVsa = ({ commit, dispatch }, initialData = {}) => {
commit(types.INITIALIZE_VSA, initialData);
const {
endpoints: { fullPath, groupPath, milestonesPath = '', labelsPath = '' },
} = initialData;
dispatch('filters/setEndpoints', {
labelsEndpoint: appendExtension(labelsPath),
milestonesEndpoint: appendExtension(milestonesPath),
groupEndpoint: groupPath,
projectEndpoint: fullPath,
});
return dispatch('setLoading', true)
.then(() => dispatch('fetchValueStreams'))
.finally(() => dispatch('setLoading', false));
......
import dateFormat from 'dateformat';
import { dateFormats } from '~/analytics/shared/constants';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { transformStagesForPathNavigation, filterStagesByHiddenStatus } from '../utils';
export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage }) => {
......@@ -20,6 +21,21 @@ export const requestParams = (state) => {
return { requestPath: fullPath, valueStreamId, stageId };
};
const filterBarParams = ({ filters }) => {
const {
authors: { selected: selectedAuthor },
milestones: { selected: selectedMilestone },
assignees: { selectedList: selectedAssigneeList },
labels: { selectedList: selectedLabelList },
} = filters;
return filterToQueryObject({
milestone_title: selectedMilestone,
author_username: selectedAuthor,
label_name: selectedLabelList,
assignee_username: selectedAssigneeList,
});
};
const dateRangeParams = ({ createdAfter, createdBefore }) => ({
created_after: createdAfter ? dateFormat(createdAfter, dateFormats.isoDate) : null,
created_before: createdBefore ? dateFormat(createdBefore, dateFormats.isoDate) : null,
......@@ -33,6 +49,7 @@ export const legacyFilterParams = ({ daysInPast }) => {
export const filterParams = (state) => {
return {
...filterBarParams(state),
...dateRangeParams(state),
};
};
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
import { formatMedianValues, calculateFormattedDayInPast } from '../utils';
import { formatMedianValues } from '../utils';
import * as types from './mutation_types';
export default {
[types.INITIALIZE_VSA](state, { endpoints, features }) {
[types.INITIALIZE_VSA](state, { endpoints, features, createdBefore, createdAfter }) {
state.endpoints = endpoints;
const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY);
state.createdBefore = now;
state.createdAfter = past;
state.createdBefore = createdBefore;
state.createdAfter = createdAfter;
state.features = features;
},
[types.SET_LOADING](state, loadingState) {
......@@ -20,11 +18,9 @@ export default {
[types.SET_SELECTED_STAGE](state, stage) {
state.selectedStage = stage;
},
[types.SET_DATE_RANGE](state, daysInPast) {
state.daysInPast = daysInPast;
const { now, past } = calculateFormattedDayInPast(daysInPast);
state.createdBefore = now;
state.createdAfter = past;
[types.SET_DATE_RANGE](state, { createdAfter, createdBefore }) {
state.createdBefore = createdBefore;
state.createdAfter = createdAfter;
},
[types.REQUEST_VALUE_STREAMS](state) {
state.valueStreams = [];
......
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
export default () => ({
id: null,
features: {},
endpoints: {},
daysInPast: DEFAULT_DAYS_TO_DISPLAY,
createdAfter: null,
createdBefore: null,
stages: [],
......
......@@ -149,3 +149,5 @@ export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
description: popoverContent[key]?.description || '',
};
});
export const appendExtension = (path) => (path.indexOf('.') > -1 ? path : `${path}.json`);
- page_title _("Value Stream Analytics")
- add_page_specific_style 'page_bundles/cycle_analytics'
- svgs = { empty_state_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_data_svg_path: image_path("illustrations/analytics/cycle-analytics-empty-chart.svg"), no_access_svg_path: image_path("illustrations/analytics/no-access.svg") }
- initial_data = { project_id: @project.id, group_path: @project.group&.path, request_path: project_cycle_analytics_path(@project), full_path: @project.full_path }.merge!(svgs)
- api_paths = @group.present? ? { milestones_path: group_milestones_path(@group), labels_path: group_labels_path(@group), group_path: group_path(@group), group_id: @group&.id } : { milestones_path: project_milestones_path(@project), labels_path: project_labels_path(@project), group_path: @project.parent&.path, group_id: @project.parent&.id }
- initial_data = { project_id: @project.id, group_path: @project.group&.path, request_path: project_cycle_analytics_path(@project), full_path: @project.full_path }.merge!(svgs, api_paths)
#js-cycle-analytics{ data: initial_data }
<script>
import { GlEmptyState } from '@gitlab/ui';
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 { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { toYmd } from '../../shared/utils';
import { METRICS_REQUESTS } from '../constants';
import DurationChart from './duration_chart.vue';
import TypeOfWorkCharts from './type_of_work_charts.vue';
......
import Api from 'ee/api';
import { removeFlash } from '~/cycle_analytics/utils';
import { removeFlash, appendExtension } from '~/cycle_analytics/utils';
import createFlash from '~/flash';
import httpStatus from '~/lib/utils/http_status';
import { __ } from '~/locale';
......@@ -9,8 +9,6 @@ export * from './actions/filters';
export * from './actions/stages';
export * from './actions/value_streams';
const appendExtension = (path) => (path.indexOf('.') > -1 ? path : `${path}.json`);
export const setPaths = ({ dispatch }, options) => {
const { groupPath, milestonesPath = '', labelsPath = '' } = options;
......
import dateFormat from 'dateformat';
import { isNumber, uniqBy } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { toYmd } from '~/analytics/shared/utils';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import createFlash from '~/flash';
......@@ -8,7 +9,6 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { newDate, dayAfter, secondsToDays, getDatesInRange } from '~/lib/utils/datetime_utility';
import httpStatus from '~/lib/utils/http_status';
import { convertToSnakeCase } from '~/lib/utils/text_utility';
import { toYmd } from '../shared/utils';
const EVENT_TYPE_LABEL = 'label';
......
......@@ -3,12 +3,6 @@ import { dateFormats } from '~/analytics/shared/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
export const toYmd = (date) => dateFormat(date, dateFormats.isoDate);
export default {
toYmd,
};
export const formattedDate = (d) => dateFormat(d, dateFormats.defaultDate);
/**
......
......@@ -8,7 +8,6 @@ import DurationChart from 'ee/analytics/cycle_analytics/components/duration_char
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 createStore from 'ee/analytics/cycle_analytics/store';
import { toYmd } from 'ee/analytics/shared/utils';
import waitForPromises from 'helpers/wait_for_promises';
import {
currentGroup,
......@@ -16,6 +15,7 @@ import {
createdAfter,
selectedProjects,
} from 'jest/cycle_analytics/mock_data';
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';
......
......@@ -9,7 +9,6 @@ import {
getTasksByTypeData,
transformRawTasksByTypeData,
} from 'ee/analytics/cycle_analytics/utils';
import { toYmd } from 'ee/analytics/shared/utils';
import { getJSONFixture } from 'helpers/fixtures';
import {
getStageByTitle,
......@@ -19,6 +18,7 @@ import {
createdAfter,
deepCamelCase,
} from 'jest/cycle_analytics/mock_data';
import { toYmd } from '~/analytics/shared/utils';
import {
PAGINATION_TYPE,
PAGINATION_SORT_DIRECTION_DESC,
......
......@@ -17,8 +17,8 @@ import {
prepareStageErrors,
formatMedianValuesWithOverview,
} from 'ee/analytics/cycle_analytics/utils';
import { toYmd } from 'ee/analytics/shared/utils';
import { createdAfter, createdBefore, rawStageMedians } from 'jest/cycle_analytics/mock_data';
import { toYmd } from '~/analytics/shared/utils';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { medianTimeToParsedSeconds } from '~/cycle_analytics/utils';
import { getDatesInRange } from '~/lib/utils/datetime_utility';
......
......@@ -5,35 +5,40 @@ require 'spec_helper'
RSpec.describe 'Value Stream Analytics', :js do
let_it_be(:user) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:stage_table_selector) { '[data-testid="vsa-stage-table"]' }
let_it_be(:stage_table_event_selector) { '[data-testid="vsa-stage-event"]' }
let_it_be(:metrics_selector) { "[data-testid='vsa-time-metrics']" }
let_it_be(:metric_value_selector) { "[data-testid='displayValue']" }
let(:stage_table) { page.find(stage_table_selector) }
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
let(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
def metrics_values
page.find(metrics_selector).all(metric_value_selector).collect(&:text)
end
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)
wait_for_all_requests
end
context 'as an allowed user' do
context 'when project is new' do
before(:all) do
project.add_maintainer(user)
end
before do
project.add_maintainer(user)
sign_in(user)
visit project_cycle_analytics_path(project)
wait_for_requests
end
it 'displays metrics' do
aggregate_failures 'with relevant values' do
expect(new_issues_counter).to have_content('-')
expect(commits_counter).to have_content('-')
expect(deploys_counter).to have_content('-')
expect(deployment_frequency_counter).to have_content('-')
end
it 'displays metrics with relevant values' do
expect(metrics_values).to eq(['-'] * 4)
end
it 'shows active stage with empty message' do
......@@ -43,14 +48,21 @@ RSpec.describe 'Value Stream Analytics', :js do
end
context "when there's value stream analytics data" do
# NOTE: in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68595 travel back
# 5 days in time before we create data for these specs, to mitigate some flakiness
# So setting the date range to be the last 2 days should skip past the existing data
from = 2.days.ago.strftime("%Y-%m-%d")
to = 1.day.ago.strftime("%Y-%m-%d")
around do |example|
travel_to(5.days.ago) { example.run }
end
before do
project.add_maintainer(user)
create_list(:issue, 2, project: project, created_at: 2.weeks.ago, milestone: milestone)
@build = create_cycle(user, project, issue, mr, milestone, pipeline)
create_cycle(user, project, issue, mr, milestone, pipeline)
deploy_master(user, project)
issue.metrics.update!(first_mentioned_in_commit_at: issue.metrics.first_associated_with_milestone_at + 1.hour)
......@@ -65,6 +77,8 @@ RSpec.describe 'Value Stream Analytics', :js do
sign_in(user)
visit project_cycle_analytics_path(project)
wait_for_requests
end
it 'displays metrics' do
......@@ -97,18 +111,20 @@ RSpec.describe 'Value Stream Analytics', :js do
expect_merge_request_to_be_present
end
context "when I change the time period observed" do
before do
_two_weeks_old_issue = create(:issue, project: project, created_at: 2.weeks.ago)
it 'can filter the issues by date' do
expect(stage_table.all(stage_table_event_selector).length).to eq(3)
click_button('Last 30 days')
click_link('Last 7 days')
wait_for_requests
end
set_daterange(from, to)
it 'shows only relevant data' do
expect(new_issue_counter).to have_content('1')
end
expect(stage_table.all(stage_table_event_selector).length).to eq(0)
end
it 'can filter the metrics by date' do
expect(metrics_values).to eq(["3.0", "2.0", "1.0", "0.0"])
set_daterange(from, to)
expect(metrics_values).to eq(['-'] * 4)
end
end
end
......@@ -141,31 +157,6 @@ RSpec.describe 'Value Stream Analytics', :js do
end
end
def find_metric_tile(sel)
page.find("#{metrics_selector} #{sel}")
end
# When now use proper pluralization for the metric names, which affects the id
def new_issue_counter
find_metric_tile("#new-issue")
end
def new_issues_counter
find_metric_tile("#new-issues")
end
def commits_counter
find_metric_tile("#commits")
end
def deploys_counter
find_metric_tile("#deploys")
end
def deployment_frequency_counter
find_metric_tile("#deployment-frequency")
end
def expect_issue_to_be_present
expect(find(stage_table_selector)).to have_content(issue.title)
expect(find(stage_table_selector)).to have_content(issue.author.name)
......
......@@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
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 ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
import initState from '~/cycle_analytics/store/state';
......@@ -30,13 +31,14 @@ Vue.use(Vuex);
let wrapper;
const { id: groupId, path: groupPath } = currentGroup;
const defaultState = {
permissions,
currentGroup,
createdBefore,
createdAfter,
stageCounts,
endpoints: { fullPath },
endpoints: { fullPath, groupId, groupPath },
};
function createStore({ initialState = {}, initialGetters = {} }) {
......@@ -74,6 +76,7 @@ function createComponent({ initialState, initialGetters } = {}) {
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');
......@@ -123,6 +126,29 @@ describe('Value stream analytics component', () => {
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);
});
......
......@@ -4,21 +4,41 @@ 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 } from '../mock_data';
import {
allowedStages,
selectedStage,
selectedValueStream,
currentGroup,
createdAfter,
createdBefore,
} from '../mock_data';
const { id: groupId, path: groupPath } = currentGroup;
const mockMilestonesPath = 'mock-milestones';
const mockLabelsPath = 'mock-labels';
const mockRequestPath = 'some/cool/path';
const mockFullPath = '/namespace/-/analytics/value_stream_analytics/value_streams';
const mockStartDate = 30;
const mockEndpoints = { fullPath: mockFullPath, requestPath: mockRequestPath };
const mockSetDateActionCommit = { payload: { startDate: mockStartDate }, type: 'SET_DATE_RANGE' };
const defaultState = { ...getters, selectedValueStream };
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 };
describe('Project Value Stream Analytics actions', () => {
let state;
let mock;
beforeEach(() => {
state = { ...defaultState };
mock = new MockAdapter(axios);
});
......@@ -34,16 +54,17 @@ describe('Project Value Stream Analytics actions', () => {
{ 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'} | ${{ startDate: mockStartDate }} | ${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 } }]}
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 } }]}
`('$action', ({ action, payload, expectedActions, expectedMutations }) => {
const types = mutationTypes(expectedMutations);
it(`will dispatch ${expectedActions} and commit ${types}`, () =>
......@@ -60,6 +81,12 @@ describe('Project Value Stream Analytics actions', () => {
let mockDispatch;
let mockCommit;
const payload = { endpoints: mockEndpoints };
const mockFilterEndpoints = {
groupEndpoint: 'foo',
labelsEndpoint: 'mock-labels.json',
milestonesEndpoint: 'mock-milestones.json',
projectEndpoint: '/namespace/-/analytics/value_stream_analytics/value_streams',
};
beforeEach(() => {
mockDispatch = jest.fn(() => Promise.resolve());
......@@ -76,6 +103,9 @@ describe('Project Value Stream Analytics actions', () => {
payload,
);
expect(mockCommit).toHaveBeenCalledWith('INITIALIZE_VSA', { endpoints: mockEndpoints });
expect(mockDispatch).toHaveBeenCalledTimes(4);
expect(mockDispatch).toHaveBeenCalledWith('filters/setEndpoints', mockFilterEndpoints);
expect(mockDispatch).toHaveBeenCalledWith('setLoading', true);
expect(mockDispatch).toHaveBeenCalledWith('fetchValueStreams');
expect(mockDispatch).toHaveBeenCalledWith('setLoading', false);
......@@ -84,7 +114,7 @@ describe('Project Value Stream Analytics actions', () => {
describe('fetchCycleAnalyticsData', () => {
beforeEach(() => {
state = { endpoints: mockEndpoints };
state = { ...defaultState, endpoints: mockEndpoints };
mock = new MockAdapter(axios);
mock.onGet(mockRequestPath).reply(httpStatusCodes.OK);
});
......@@ -129,7 +159,6 @@ describe('Project Value Stream Analytics actions', () => {
state = {
...defaultState,
endpoints: mockEndpoints,
startDate: mockStartDate,
selectedStage,
};
mock = new MockAdapter(axios);
......@@ -152,7 +181,6 @@ describe('Project Value Stream Analytics actions', () => {
state = {
...defaultState,
endpoints: mockEndpoints,
startDate: mockStartDate,
selectedStage,
};
mock = new MockAdapter(axios);
......@@ -177,7 +205,6 @@ describe('Project Value Stream Analytics actions', () => {
state = {
...defaultState,
endpoints: mockEndpoints,
startDate: mockStartDate,
selectedStage,
};
mock = new MockAdapter(axios);
......
import { useFakeDate } from 'helpers/fake_date';
import { DEFAULT_DAYS_TO_DISPLAY } from '~/cycle_analytics/constants';
import * as types from '~/cycle_analytics/store/mutation_types';
import mutations from '~/cycle_analytics/store/mutations';
import {
......@@ -65,15 +64,16 @@ describe('Project Value Stream Analytics mutations', () => {
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 },
createdAfter: mockCreatedAfter,
createdBefore: mockCreatedBefore,
...mockSetDatePayload,
};
it.each`
......@@ -89,9 +89,8 @@ describe('Project Value Stream Analytics mutations', () => {
it.each`
mutation | payload | stateKey | value
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'daysInPast'} | ${DEFAULT_DAYS_TO_DISPLAY}
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdAfter'} | ${mockCreatedAfter}
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdBefore'} | ${mockCreatedBefore}
${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}
......
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