Commit 89c07866 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Added filter bar to project VSA

Adds a filter bar to project VSA to support
filtering at the project level

Update related jest specs

Adds updated specs for the cycle analytics filter
bar in project level VSA.

Changelog: added
parent 5a614293
......@@ -4,6 +4,7 @@ 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';
......@@ -18,6 +19,7 @@ export default {
GlSprintf,
PathNavigation,
StageTable,
ValueStreamFilters,
ValueStreamMetrics,
},
props: {
......@@ -50,6 +52,8 @@ export default {
'stageCounts',
'endpoints',
'features',
'createdBefore',
'createdAfter',
]),
...mapGetters(['pathNavigationData', 'filterParams']),
displayStageEvents() {
......@@ -157,6 +161,13 @@ export default {
</div>
</div>
</div>
<value-stream-filters
class="gl-w-full"
:group-id="endpoints.groupId"
:group-path="endpoints.groupPath"
:has-project-filter="false"
:has-date-range-filter="false"
/>
<value-stream-metrics
:request-path="endpoints.fullPath"
:request-params="filterParams"
......
......@@ -14,15 +14,21 @@ export default () => {
requestPath,
fullPath,
projectId,
groupId,
groupPath,
labelsPath,
milestonesPath,
} = el.dataset;
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),
......
......@@ -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) => {
......@@ -178,6 +179,16 @@ export const setDateRange = ({ dispatch, commit }, daysInPast) => {
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),
};
};
......@@ -149,3 +149,6 @@ export const prepareTimeMetricsData = (data = [], popoverContent = {}) =>
description: popoverContent[key]?.description || '',
};
});
// TODO: maybe use paths.join
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 }
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 +8,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;
......
......@@ -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 findFilterBar = () => wrapper.findComponent(ValueStreamFilters);
const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
const findStageTable = () => wrapper.findComponent(StageTable);
const findStageEvents = () => findStageTable().props('stageEvents');
......@@ -123,6 +126,17 @@ describe('Value stream analytics component', () => {
expect(findStageEvents()).toEqual(selectedStageEvents);
});
it('renders the filter bar', () => {
expect(findFilterBar().exists()).toBe(true);
});
it('hides the project selector and range selector', () => {
expect(findFilterBar().props()).toMatchObject({
hasProjectFilter: false,
hasDateRangeFilter: false,
});
});
it('does not render the loading icon', () => {
expect(findLoadingIcon().exists()).toBe(false);
});
......
......@@ -4,12 +4,22 @@ 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 } 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 mockEndpoints = {
fullPath: mockFullPath,
requestPath: mockRequestPath,
labelsPath: mockLabelsPath,
milestonesPath: mockMilestonesPath,
groupId,
groupPath,
};
const mockSetDateActionCommit = { payload: { startDate: mockStartDate }, type: 'SET_DATE_RANGE' };
const defaultState = { ...getters, selectedValueStream };
......@@ -60,6 +70,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 +92,7 @@ describe('Project Value Stream Analytics actions', () => {
payload,
);
expect(mockCommit).toHaveBeenCalledWith('INITIALIZE_VSA', { endpoints: mockEndpoints });
expect(mockDispatch).toHaveBeenCalledWith('filters/setEndpoints', mockFilterEndpoints);
expect(mockDispatch).toHaveBeenCalledWith('setLoading', true);
expect(mockDispatch).toHaveBeenCalledWith('fetchValueStreams');
expect(mockDispatch).toHaveBeenCalledWith('setLoading', false);
......
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