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