Commit c72565ae authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'fix-cycle-analytics-403-error-message' into 'master'

Fix display 403 errors for cycle analytics

See merge request gitlab-org/gitlab!22987
parents 3964175f d7143c36
......@@ -85,9 +85,15 @@ export default {
return this.selectedGroup && !this.errorCode;
},
shouldDisplayDurationChart() {
return !this.isLoadingDurationChart && !this.isLoading;
return this.featureFlags.hasDurationChart && !this.hasNoAccessError;
},
shouldDisplayTasksByTypeChart() {
return this.featureFlags.hasTasksByTypeChart && !this.hasNoAccessError;
},
isDurationChartLoaded() {
return !this.isLoadingDurationChart && !this.isLoading;
},
isTasksByTypeChartLoaded() {
return !this.isLoading && !this.isLoadingTasksByTypeChart;
},
hasDateRangeSet() {
......@@ -280,8 +286,8 @@ export default {
/>
</div>
</div>
<template v-if="featureFlags.hasDurationChart">
<template v-if="shouldDisplayDurationChart">
<template v-if="isDurationChartLoaded">
<div class="mt-3 d-flex">
<h4 class="mt-0">{{ s__('CycleAnalytics|Days to completion') }}</h4>
<stage-dropdown-filter
......@@ -304,9 +310,9 @@ export default {
</template>
<gl-loading-icon v-else-if="!isLoading" size="md" class="my-4 py-4" />
</template>
<template v-if="featureFlags.hasTasksByTypeChart">
<template v-if="shouldDisplayTasksByTypeChart">
<div class="js-tasks-by-type-chart">
<div v-if="shouldDisplayTasksByTypeChart">
<div v-if="isTasksByTypeChartLoaded">
<tasks-by-type-chart
:chart-data="tasksByTypeChartData"
:filters="selectedTasksByTypeFilters"
......
......@@ -11,6 +11,14 @@ const removeError = () => {
hideFlash(flashEl);
}
};
const handleErrorOrRethrow = ({ action, error }) => {
if (error?.response?.status === httpStatus.FORBIDDEN) {
throw error;
}
action();
};
export const setFeatureFlags = ({ commit }, featureFlags) =>
commit(types.SET_FEATURE_FLAGS, featureFlags);
export const setSelectedGroup = ({ commit }, group) => commit(types.SET_SELECTED_GROUP, group);
......@@ -85,7 +93,12 @@ export const fetchStageMedianValues = ({ state, dispatch, getters }) => {
return Promise.all(stageIds.map(stageId => fetchStageMedian(currentGroupPath, stageId, params)))
.then(data => dispatch('receiveStageMedianValuesSuccess', data))
.catch(err => dispatch('receiveStageMedianValuesError', err));
.catch(error =>
handleErrorOrRethrow({
error,
action: () => dispatch('receiveStageMedianValuesError', error),
}),
);
};
export const requestCycleAnalyticsData = ({ commit }) => commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
......@@ -150,7 +163,9 @@ export const fetchSummaryData = ({ state, dispatch, getters }) => {
return Api.cycleAnalyticsSummaryData({ group_id: fullPath, created_after, created_before })
.then(({ data }) => dispatch('receiveSummaryDataSuccess', data))
.catch(error => dispatch('receiveSummaryDataError', error));
.catch(error =>
handleErrorOrRethrow({ error, action: () => dispatch('receiveSummaryDataError', error) }),
);
};
export const requestGroupStagesAndEvents = ({ commit }) =>
......@@ -174,7 +189,9 @@ export const fetchGroupLabels = ({ dispatch, state }) => {
return Api.groupLabels(fullPath)
.then(data => dispatch('receiveGroupLabelsSuccess', data))
.catch(error => dispatch('receiveGroupLabelsError', error));
.catch(error =>
handleErrorOrRethrow({ error, action: () => dispatch('receiveGroupLabelsError', error) }),
);
};
export const receiveGroupStagesAndEventsError = ({ commit }, error) => {
......@@ -209,7 +226,12 @@ export const fetchGroupStagesAndEvents = ({ state, dispatch, getters }) => {
nestQueryStringKeys({ start_date: created_after, project_ids }, 'cycle_analytics'),
)
.then(({ data }) => dispatch('receiveGroupStagesAndEventsSuccess', data))
.catch(error => dispatch('receiveGroupStagesAndEventsError', error));
.catch(error =>
handleErrorOrRethrow({
error,
action: () => dispatch('receiveGroupStagesAndEventsError', error),
}),
);
};
export const requestCreateCustomStage = ({ commit }) => commit(types.REQUEST_CREATE_CUSTOM_STAGE);
......
......@@ -14,6 +14,7 @@ import 'bootstrap';
import '~/gl_dropdown';
import Scatterplot from 'ee/analytics/shared/components/scatterplot.vue';
import Daterange from 'ee/analytics/shared/components/daterange.vue';
import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type_chart.vue';
import waitForPromises from 'helpers/wait_for_promises';
import httpStatusCodes from '~/lib/utils/http_status';
import * as mockData from '../mock_data';
......@@ -105,6 +106,10 @@ describe('Cycle Analytics component', () => {
expect(wrapper.find(Scatterplot).exists()).toBe(flag);
};
const displaysTasksByType = flag => {
expect(wrapper.find(TasksByTypeChart).exists()).toBe(flag);
};
beforeEach(() => {
mock = new MockAdapter(axios);
wrapper = createComponent();
......@@ -289,11 +294,10 @@ describe('Cycle Analytics component', () => {
describe('the user does not have access to the group', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
wrapper.vm.$store.dispatch('setSelectedGroup', {
...mockData.group,
});
mock.onAny().reply(403);
wrapper.vm.$store.state.errorCode = 403;
wrapper.vm.onGroupSelect(mockData.group);
return waitForPromises();
});
it('renders the no access information', () => {
......@@ -322,6 +326,14 @@ describe('Cycle Analytics component', () => {
it('does not display the add stage button', () => {
expect(wrapper.find('.js-add-stage-button').exists()).toBe(false);
});
it('does not display the tasks by type chart', () => {
displaysTasksByType(false);
});
it('does not display the duration chart', () => {
displaysDurationScatterPlot(false);
});
});
describe('with customizableCycleAnalytics=true', () => {
......@@ -366,6 +378,7 @@ describe('Cycle Analytics component', () => {
wrapper.destroy();
mock.restore();
});
it('displays the tasks by type chart', () => {
expect(wrapper.find('.js-tasks-by-type-chart').exists()).toBe(true);
});
......
......@@ -60,6 +60,7 @@ describe('TasksByTypeChart', () => {
},
});
});
it('should render the no data available message', () => {
expect(wrapper.html()).toMatchSnapshot();
});
......
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