Commit ab9dbbb2 authored by Phil Hughes's avatar Phil Hughes

Merge branch '208159-fe-group-level-activity-overview-mvc' into 'master'

Resolve "[FE] Group-level activity overview: MVC"

Closes #208159

See merge request gitlab-org/gitlab!26285
parents c4fec8cd ffdce821
<script>
import Api from 'ee/api';
import { __, s__ } from '~/locale';
import createFlash from '~/flash';
import MetricCard from '../../shared/components/metric_card.vue';
export default {
name: 'GroupActivityCard',
components: {
MetricCard,
},
props: {
groupFullPath: {
type: String,
required: true,
},
},
data() {
return {
isLoading: false,
metrics: {
mergeRequests: { value: null, label: s__('GroupActivyMetrics|Merge Requests created') },
issues: { value: null, label: s__('GroupActivyMetrics|Issues created') },
},
};
},
computed: {
metricsArray() {
return Object.entries(this.metrics).map(([key, obj]) => {
const { value, label } = obj;
return {
key,
value,
label,
};
});
},
},
created() {
this.fetchMetrics(this.groupFullPath);
},
methods: {
fetchMetrics(groupPath) {
this.isLoading = true;
return Promise.all([
Api.groupActivityMergeRequestsCount(groupPath),
Api.groupActivityIssuesCount(groupPath),
])
.then(([mrResponse, issuesResponse]) => {
this.metrics.mergeRequests.value = mrResponse.data.merge_requests_count;
this.metrics.issues.value = issuesResponse.data.issues_count;
this.isLoading = false;
})
.catch(() => {
createFlash(__('Failed to load group activity metrics. Please try again.'));
this.isLoading = false;
});
},
},
};
</script>
<template>
<div></div>
<metric-card
:title="s__('GroupActivyMetrics|Recent activity (last 90 days)')"
:metrics="metricsArray"
:is-loading="isLoading"
/>
</template>
......@@ -24,6 +24,8 @@ export default {
'/-/analytics/value_stream_analytics/stages/:stage_id/duration_chart',
cycleAnalyticsGroupLabelsPath: '/api/:version/groups/:namespace_path/labels',
codeReviewAnalyticsPath: '/api/:version/analytics/code_review',
groupActivityIssuesPath: '/api/:version/analytics/group_activity/issues_count',
groupActivityMergeRequestsPath: '/api/:version/analytics/group_activity/merge_requests_count',
countriesPath: '/-/countries',
countryStatesPath: '/-/country_states',
paymentFormPath: '/-/subscriptions/payment_form',
......@@ -216,6 +218,16 @@ export default {
return axios.get(url, { params });
},
groupActivityMergeRequestsCount(groupPath) {
const url = Api.buildUrl(this.groupActivityMergeRequestsPath);
return axios.get(url, { params: { group_path: groupPath } });
},
groupActivityIssuesCount(groupPath) {
const url = Api.buildUrl(this.groupActivityIssuesPath);
return axios.get(url, { params: { group_path: groupPath } });
},
getGeoReplicableItems(replicable, params = {}) {
const url = Api.buildUrl(this.geoReplicationPath).replace(':replicable', replicable);
return axios.get(url, { params });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GroupActivity component matches the snapshot 1`] = `
<div
class="card"
>
<!---->
<div
class="card-header"
>
<strong>
Recent activity (last 90 days)
</strong>
</div>
<div
class="card-body"
>
<!---->
<!---->
<div
class="d-flex"
>
<div
class="flex-grow text-center"
>
<h3
class="my-2"
>
10
</h3>
<p
class="text-secondary gl-font-size-small mb-2"
>
Merge Requests created
</p>
</div>
<div
class="flex-grow text-center"
>
<h3
class="my-2"
>
20
</h3>
<p
class="text-secondary gl-font-size-small mb-2"
>
Issues created
</p>
</div>
</div>
</div>
<!---->
<!---->
</div>
`;
import { mount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import Api from 'ee/api';
import GroupActivityCard from 'ee/analytics/group_analytics/components/group_activity_card.vue';
import MetricCard from 'ee/analytics/shared/components/metric_card.vue';
import waitForPromises from 'helpers/wait_for_promises';
const TEST_GROUP_ID = 'gitlab-org';
const TEST_MERGE_REQUESTS_COUNT = { data: { merge_requests_count: 10 } };
const TEST_ISSUES_COUNT = { data: { issues_count: 20 } };
describe('GroupActivity component', () => {
let wrapper;
let mock;
const createComponent = () => {
wrapper = mount(GroupActivityCard, {
propsData: {
groupFullPath: TEST_GROUP_ID,
},
});
};
beforeEach(() => {
mock = new MockAdapter(axios);
jest
.spyOn(Api, 'groupActivityMergeRequestsCount')
.mockReturnValue(Promise.resolve(TEST_MERGE_REQUESTS_COUNT));
jest.spyOn(Api, 'groupActivityIssuesCount').mockReturnValue(Promise.resolve(TEST_ISSUES_COUNT));
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
const findMetricCard = () => wrapper.find(MetricCard);
it('matches the snapshot', () => {
createComponent();
return wrapper.vm
.$nextTick()
.then(waitForPromises)
.then(() => {
expect(wrapper.element).toMatchSnapshot();
});
});
it('fetches MR and issue count and updates isLoading properly', () => {
createComponent();
expect(wrapper.vm.isLoading).toBe(true);
return wrapper.vm
.$nextTick()
.then(() => {
expect(Api.groupActivityMergeRequestsCount).toHaveBeenCalledWith(TEST_GROUP_ID);
expect(Api.groupActivityIssuesCount).toHaveBeenCalledWith(TEST_GROUP_ID);
waitForPromises();
})
.then(() => {
expect(wrapper.vm.isLoading).toBe(false);
expect(wrapper.vm.metrics.mergeRequests.value).toBe(10);
expect(wrapper.vm.metrics.issues.value).toBe(20);
});
});
it('passes the metrics array to the metric card', () => {
createComponent();
return wrapper.vm
.$nextTick()
.then(waitForPromises)
.then(() => {
expect(findMetricCard().props('metrics')).toEqual([
{ key: 'mergeRequests', value: 10, label: 'Merge Requests created' },
{ key: 'issues', value: 20, label: 'Issues created' },
]);
});
});
});
......@@ -550,6 +550,42 @@ describe('Api', () => {
});
});
describe('GroupActivityAnalytics', () => {
const groupId = 'gitlab-org';
describe('groupActivityMergeRequestsCount', () => {
it('fetches the number of MRs created for a given group', () => {
const response = { merge_requests_count: 10 };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/analytics/group_activity/merge_requests_count`;
jest.spyOn(Api, 'buildUrl').mockReturnValue(expectedUrl);
jest.spyOn(axios, 'get');
mock.onGet(expectedUrl).reply(200, response);
return Api.groupActivityMergeRequestsCount(groupId).then(({ data }) => {
expect(data).toEqual(response);
expect(axios.get).toHaveBeenCalledWith(expectedUrl, { params: { group_path: groupId } });
});
});
});
describe('groupActivityIssuesCount', () => {
it('fetches the number of issues created for a given group', () => {
const response = { issues_count: 20 };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/analytics/group_activity/issues_count`;
jest.spyOn(Api, 'buildUrl').mockReturnValue(expectedUrl);
jest.spyOn(axios, 'get');
mock.onGet(expectedUrl).replyOnce(200, response);
return Api.groupActivityIssuesCount(groupId).then(({ data }) => {
expect(data).toEqual(response);
expect(axios.get).toHaveBeenCalledWith(expectedUrl, { params: { group_path: groupId } });
});
});
});
});
describe('GeoReplicable', () => {
let expectedUrl;
let apiResponse;
......
......@@ -8473,6 +8473,9 @@ msgstr ""
msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
msgid "Failed to load group activity metrics. Please try again."
msgstr ""
msgid "Failed to load groups & users."
msgstr ""
......@@ -10030,6 +10033,15 @@ msgstr ""
msgid "Group: %{name}"
msgstr ""
msgid "GroupActivyMetrics|Issues created"
msgstr ""
msgid "GroupActivyMetrics|Merge Requests created"
msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgstr ""
msgid "GroupRoadmap|%{dateWord} – No end date"
msgstr ""
......
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