Commit f4a9ac7b authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'fetch-ca-summary-from-analytics-endpoint' into 'master'

Fetch ca summary data from analytics endpoint

See merge request gitlab-org/gitlab!22507
parents 329c4be9 a04cdb05
......@@ -139,17 +139,16 @@ export const receiveSummaryDataSuccess = ({ commit }, data) =>
commit(types.RECEIVE_SUMMARY_DATA_SUCCESS, data);
export const fetchSummaryData = ({ state, dispatch, getters }) => {
const { cycleAnalyticsRequestParams = {} } = getters;
const {
cycleAnalyticsRequestParams: { created_after, created_before },
} = getters;
dispatch('requestSummaryData');
const {
selectedGroup: { fullPath },
} = state;
return Api.cycleAnalyticsSummaryData(
fullPath,
nestQueryStringKeys(cycleAnalyticsRequestParams, 'cycle_analytics'),
)
return Api.cycleAnalyticsSummaryData({ group_id: fullPath, created_after, created_before })
.then(({ data }) => dispatch('receiveSummaryDataSuccess', data))
.catch(error => dispatch('receiveSummaryDataError', error));
};
......
......@@ -111,8 +111,7 @@ export default {
state.summary = [];
},
[types.RECEIVE_SUMMARY_DATA_SUCCESS](state, data) {
const { summary } = data;
state.summary = summary.map(item => ({
state.summary = data.map(item => ({
...item,
value: item.value || '-',
}));
......
......@@ -16,7 +16,7 @@ export default {
projectPackagesPath: '/api/:version/projects/:id/packages',
projectPackagePath: '/api/:version/projects/:id/packages/:package_id',
cycleAnalyticsTasksByTypePath: '/-/analytics/type_of_work/tasks_by_type',
cycleAnalyticsSummaryDataPath: '/groups/:group_id/-/cycle_analytics',
cycleAnalyticsSummaryDataPath: '/-/analytics/cycle_analytics/summary',
cycleAnalyticsGroupStagesAndEventsPath: '/-/analytics/cycle_analytics/stages',
cycleAnalyticsStageEventsPath: '/-/analytics/cycle_analytics/stages/:stage_id/records',
cycleAnalyticsStageMedianPath: '/-/analytics/cycle_analytics/stages/:stage_id/median',
......@@ -144,9 +144,8 @@ export default {
return axios.get(url, { params });
},
cycleAnalyticsSummaryData(groupId, params = {}) {
const url = Api.buildUrl(this.cycleAnalyticsSummaryDataPath).replace(':group_id', groupId);
cycleAnalyticsSummaryData(params = {}) {
const url = Api.buildUrl(this.cycleAnalyticsSummaryDataPath);
return axios.get(url, { params });
},
......
......@@ -21,11 +21,9 @@ module Analytics
private
def group_params
{
created_after: request_params.created_after,
created_before: request_params.created_before,
project_ids: request_params.project_ids
}
hash = { created_after: request_params.created_after, created_before: request_params.created_before }
hash[:project_ids] = request_params.project_ids if request_params.project_ids.any?
hash
end
def validate_params
......
......@@ -25,6 +25,24 @@ describe Analytics::CycleAnalytics::SummaryController do
expect(response).to match_response_schema('analytics/cycle_analytics/summary', dir: 'ee')
end
it 'omits `projects` parameter if it is not given' do
expect(CycleAnalytics::GroupLevel).to receive(:new).with(group: group, options: hash_excluding(:projects)).and_call_original
subject
expect(response).to be_successful
end
it 'contains `projects` parameter' do
params[:project_ids] = [-1]
expect(CycleAnalytics::GroupLevel).to receive(:new).with(group: group, options: hash_including(:projects)).and_call_original
subject
expect(response).to be_successful
end
include_examples 'cycle analytics data endpoint examples'
include_examples 'group permission check on the controller level'
end
......
......@@ -363,8 +363,8 @@ describe('Cycle Analytics component', () => {
const defaultRequests = {
fetchSummaryData: {
status: defaultStatus,
endpoint: `/groups/${groupId}/-/cycle_analytics`,
response: { ...mockData.cycleAnalyticsData },
endpoint: `/-/analytics/cycle_analytics/summary`,
response: [...mockData.summaryData],
},
fetchGroupStagesAndEvents: {
status: defaultStatus,
......@@ -435,7 +435,7 @@ describe('Cycle Analytics component', () => {
overrides: {
fetchSummaryData: {
status: httpStatusCodes.NOT_FOUND,
endpoint: `/groups/${groupId}/-/cycle_analytics`,
endpoint: '/-/analytics/cycle_analytics/summary',
response: { response: { status: httpStatusCodes.NOT_FOUND } },
},
},
......
......@@ -7,15 +7,11 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getDateInPast } from '~/lib/utils/datetime_utility';
import { mockLabels } from '../../../../../spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data';
/*
* With the new API endpoints (analytics/cycle_analytics) we will
* fetch stages, cycleEvents and summary data from different endpoints
*/
const endpoints = {
cycleAnalyticsData: 'cycle_analytics/mock_data.json', // existing cycle analytics data
customizableCycleAnalyticsStagesAndEvents: 'analytics/cycle_analytics/stages.json', // customizable stages and events endpoint
stageEvents: stage => `analytics/cycle_analytics/stages/${stage}/records.json`,
stageMedian: stage => `analytics/cycle_analytics/stages/${stage}/median.json`,
summaryData: 'analytics/cycle_analytics/summary.json',
};
export const groupLabels = mockLabels.map(({ title, ...rest }) => ({ ...rest, name: title }));
......@@ -31,7 +27,7 @@ export const group = {
const getStageByTitle = (stages, title) =>
stages.find(stage => stage.title && stage.title.toLowerCase().trim() === title) || {};
export const cycleAnalyticsData = getJSONFixture(endpoints.cycleAnalyticsData);
export const summaryData = getJSONFixture(endpoints.summaryData);
export const customizableStagesAndEvents = getJSONFixture(
endpoints.customizableCycleAnalyticsStagesAndEvents,
......
......@@ -7,7 +7,7 @@ import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import createFlash from '~/flash';
import {
group,
cycleAnalyticsData,
summaryData,
allowedStages as stages,
groupLabels,
startDate,
......@@ -25,7 +25,7 @@ const [selectedStage] = stages;
const selectedStageSlug = selectedStage.slug;
const endpoints = {
groupLabels: `/groups/${group.path}/-/labels`,
cycleAnalyticsData: `/groups/${group.path}/-/cycle_analytics`,
summaryData: '/analytics/cycle_analytics/summary',
durationData: /analytics\/cycle_analytics\/stages\/\d+\/duration_chart/,
stageData: /analytics\/cycle_analytics\/stages\/\d+\/records/,
stageMedian: /analytics\/cycle_analytics\/stages\/\d+\/median/,
......@@ -268,7 +268,7 @@ describe('Cycle analytics actions', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
mock.onGet(endpoints.cycleAnalyticsData).replyOnce(200, cycleAnalyticsData);
mock.onGet(endpoints.summaryData).replyOnce(200, summaryData);
state = { ...state, selectedGroup, startDate, endDate };
});
......
......@@ -3,7 +3,7 @@ import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import {
cycleAnalyticsData,
summaryData,
rawIssueEvents,
issueEvents as transformedEvents,
issueStage,
......@@ -78,7 +78,6 @@ describe('Cycle analytics mutations', () => {
'$mutation with payload $payload will update state with $expectedState',
({ mutation, payload, expectedState }) => {
state = {
endpoints: { cycleAnalyticsData: '/fake/api' },
selectedGroup: { fullPath: 'rad-stage' },
};
mutations[mutation](state, payload);
......@@ -167,15 +166,12 @@ describe('Cycle analytics mutations', () => {
describe(`${types.RECEIVE_SUMMARY_DATA_SUCCESS}`, () => {
beforeEach(() => {
state = { stages: [{ slug: 'plan' }, { slug: 'issue' }, { slug: 'test' }] };
mutations[types.RECEIVE_SUMMARY_DATA_SUCCESS](state, {
...cycleAnalyticsData,
summary: [{ value: 0, title: 'New Issues' }, { value: 0, title: 'Deploys' }],
});
mutations[types.RECEIVE_SUMMARY_DATA_SUCCESS](state, summaryData);
});
it('will set each summary item with a value of 0 to "-"', () => {
expect(state.summary).toEqual([
{ value: '-', title: 'New Issues' },
{ value: 3, title: 'New Issues' },
{ value: '-', title: 'Deploys' },
]);
});
......
......@@ -319,6 +319,11 @@ describe('Api', () => {
const createdBefore = '2019-11-18';
const createdAfter = '2019-08-18';
const stageId = 'thursday';
const defaultParams = {
group_id: groupId,
created_after: createdAfter,
created_before: createdBefore,
};
const expectRequestWithCorrectParameters = (responseObj, { params, expectedUrl, response }) => {
const {
......@@ -351,9 +356,7 @@ describe('Api', () => {
];
const labelIds = [10, 9, 8, 7];
const params = {
group_id: groupId,
created_after: createdAfter,
created_before: createdBefore,
...defaultParams,
project_ids: null,
subject: cycleAnalyticsConstants.TASKS_BY_TYPE_SUBJECT_ISSUE,
label_ids: labelIds,
......@@ -372,16 +375,16 @@ describe('Api', () => {
});
describe('cycleAnalyticsSummaryData', () => {
it('fetches cycle analytics summary, stage stats and permissions data', done => {
const response = { summary: [], stats: [], permissions: {} };
it('fetches cycle analytics summary data', done => {
const response = [{ value: 0, title: 'New Issues' }, { value: 0, title: 'Deploys' }];
const params = {
'cycle_analytics[created_after]': createdAfter,
'cycle_analytics[created_before]': createdBefore,
...defaultParams,
};
const expectedUrl = `${dummyUrlRoot}/groups/${groupId}/-/cycle_analytics`;
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/summary`;
mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsSummaryData(groupId, params)
Api.cycleAnalyticsSummaryData(params)
.then(responseObj =>
expectRequestWithCorrectParameters(responseObj, {
response,
......@@ -422,9 +425,7 @@ describe('Api', () => {
it('fetches stage events', done => {
const response = { events: [] };
const params = {
group_id: groupId,
created_after: createdAfter,
created_before: createdBefore,
...defaultParams,
};
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}/records`;
mock.onGet(expectedUrl).reply(200, response);
......@@ -446,9 +447,7 @@ describe('Api', () => {
it('fetches stage events', done => {
const response = { value: '5 days ago' };
const params = {
group_id: groupId,
created_after: createdAfter,
created_before: createdBefore,
...defaultParams,
};
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/${stageId}/median`;
mock.onGet(expectedUrl).reply(200, response);
......@@ -535,9 +534,7 @@ describe('Api', () => {
it('fetches stage duration data', done => {
const response = [];
const params = {
group_id: groupId,
created_after: createdAfter,
created_before: createdBefore,
...defaultParams,
};
const expectedUrl = `${dummyUrlRoot}/-/analytics/cycle_analytics/stages/thursday/duration_chart`;
mock.onGet(expectedUrl).reply(200, response);
......
......@@ -42,7 +42,9 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
create_merge_request_closing_issue(user, project, issue_1)
create_merge_request_closing_issue(user, project, issue_2)
merge_merge_requests_closing_issue(user, project, issue_3)
end
def create_deployment
deploy_master(user, project, environment: 'staging')
deploy_master(user, project)
end
......@@ -93,6 +95,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
stub_licensed_features(cycle_analytics_for_groups: true)
prepare_cycle_analytics_data
create_deployment
sign_in(user)
end
......@@ -113,6 +116,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
stub_licensed_features(cycle_analytics_for_groups: true)
prepare_cycle_analytics_data
create_deployment
sign_in(user)
end
......@@ -142,6 +146,7 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end
prepare_cycle_analytics_data
create_deployment
additional_cycle_analytics_metrics
......@@ -171,6 +176,27 @@ describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end
end
describe Analytics::CycleAnalytics::SummaryController, type: :controller do
render_views
let(:params) { { created_after: 3.months.ago, created_before: Time.now, group_id: group.full_path } }
before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(cycle_analytics_for_groups: true)
prepare_cycle_analytics_data
sign_in(user)
end
it 'analytics/cycle_analytics/summary.json' do
get(:show, params: params, format: :json)
expect(response).to be_successful
end
end
describe Analytics::TasksByTypeController, type: :controller do
render_views
......
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