Commit eac1bfe8 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '218459-group-activity-mrs-chart-w-single-series-app-skeleton' into 'master'

Resolve "Group Activity - MRs Chart w/ Single Series: App skeleton"

See merge request gitlab-org/gitlab!35143
parents 381dbae8 b6189c2d
......@@ -2,26 +2,27 @@
import Api from 'ee/api';
import { __, s__ } from '~/locale';
import createFlash from '~/flash';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import MetricCard from '../../shared/components/metric_card.vue';
const ENABLED_REPORT_PAGES = ['mergeRequests'];
export default {
name: 'GroupActivityCard',
components: {
MetricCard,
},
props: {
groupFullPath: {
type: String,
required: true,
},
},
inject: ['groupFullPath', 'groupName', 'reportPagesPath', 'enableReportPages'],
data() {
return {
isLoading: false,
metrics: {
mergeRequests: { value: null, label: s__('GroupActivyMetrics|Merge Requests created') },
issues: { value: null, label: s__('GroupActivyMetrics|Issues created') },
newMembers: { value: null, label: s__('GroupActivityMetrics|New Members created') },
mergeRequests: {
value: null,
label: s__('GroupActivityMetrics|Merge Requests opened'),
},
issues: { value: null, label: s__('GroupActivityMetrics|Issues opened') },
newMembers: { value: null, label: s__('GroupActivityMetrics|Members added') },
},
};
},
......@@ -33,6 +34,7 @@ export default {
key,
value,
label,
link: this.generateReportPageLink(key),
};
});
},
......@@ -60,13 +62,27 @@ export default {
this.isLoading = false;
});
},
displayReportLink(key) {
return this.enableReportPages && ENABLED_REPORT_PAGES.includes(key);
},
generateReportPageLink(key) {
return this.displayReportLink(key)
? mergeUrlParams(
{
groupPath: this.groupFullPath,
groupName: this.groupName,
},
this.reportPagesPath,
)
: null;
},
},
};
</script>
<template>
<metric-card
:title="s__('GroupActivyMetrics|Recent activity (last 90 days)')"
:title="s__('GroupActivityMetrics|Recent activity (last 90 days)')"
:metrics="metricsArray"
:is-loading="isLoading"
/>
......
......@@ -6,17 +6,20 @@ export default () => {
if (!container) return;
const { groupFullPath } = container.dataset;
const { groupFullPath, groupName, reportPagesPath } = container.dataset;
const { reportPages: enableReportPages } = gon?.features;
// eslint-disable-next-line no-new
new Vue({
el: container,
render(h) {
return h(GroupActivityCard, {
props: {
provide: {
groupFullPath,
groupName,
reportPagesPath,
enableReportPages,
},
});
render(h) {
return h(GroupActivityCard);
},
});
};
<script>
import { GlBreadcrumb, GlIcon } from '@gitlab/ui';
import { queryToObject } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
export default {
name: 'ReportsApp',
components: {
GlBreadcrumb,
GlIcon,
},
methods: {
breadcrumbs() {
const { groupName = null, groupPath = null } = queryToObject(document.location.search);
return [
groupName && groupPath ? { text: groupName, href: `/${groupPath}` } : null,
{ text: s__('GenericReports|Report'), href: '' },
].filter(Boolean);
},
},
};
</script>
<template>
<div>
<gl-breadcrumb :items="breadcrumbs()">
<template #separator>
<gl-icon name="angle-right" :size="8" />
</template>
</gl-breadcrumb>
</div>
</template>
import Vue from 'vue';
import ReportsApp from './components/app.vue';
export default () => {
const el = document.querySelector('#js-reports-app');
if (!el) return false;
return new Vue({
el,
name: 'ReportsApp',
render: createElement =>
createElement(ReportsApp, {
props: {},
}),
});
};
<script>
import { GlCard, GlSkeletonLoading } from '@gitlab/ui';
import { GlCard, GlSkeletonLoading, GlLink } from '@gitlab/ui';
export default {
name: 'MetricCard',
components: {
GlCard,
GlSkeletonLoading,
GlLink,
},
props: {
title: {
......@@ -45,7 +46,10 @@ export default {
ref="metricItem"
class="js-metric-card-item flex-grow text-center"
>
<h3 class="my-2">{{ valueText(metric) }}</h3>
<gl-link v-if="metric.link" :href="metric.link">
<h3 class="gl-my-2 gl-text-blue-700">{{ valueText(metric) }}</h3>
</gl-link>
<h3 v-else class="gl-my-2">{{ valueText(metric) }}</h3>
<p class="text-secondary gl-font-sm mb-2">{{ metric.label }}</p>
</div>
</div>
......
import initReportsApp from 'ee/analytics/reports';
document.addEventListener('DOMContentLoaded', () => {
initReportsApp();
});
......@@ -13,6 +13,10 @@ module EE
before_action only: :issues do
push_frontend_feature_flag(:scoped_labels, @group)
end
before_action only: :show do
push_frontend_feature_flag(:report_pages)
end
end
override :render_show_html
......
- @hide_breadcrumbs = true
- page_title _("Reports")
#js-reports-app
- return unless show_group_activity_analytics?
#js-group-activity{ data: { group_full_path: @group.full_path } }
#js-group-activity{ data: { group_full_path: @group.full_path, group_name: @group.name, report_pages_path: analytics_report_pages_path } }
......@@ -25,7 +25,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
4
</h3>
......@@ -40,7 +40,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
-
</h3>
......@@ -55,7 +55,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
-
</h3>
......
......@@ -25,7 +25,7 @@ exports[`GroupActivity component matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
10
</h3>
......@@ -33,14 +33,14 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p
class="text-secondary gl-font-sm mb-2"
>
Merge Requests created
Merge Requests opened
</p>
</div>
<div
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
20
</h3>
......@@ -48,14 +48,14 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p
class="text-secondary gl-font-sm mb-2"
>
Issues created
Issues opened
</p>
</div>
<div
class="js-metric-card-item flex-grow text-center"
>
<h3
class="my-2"
class="gl-my-2"
>
30
</h3>
......@@ -63,7 +63,7 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p
class="text-secondary gl-font-sm mb-2"
>
New Members created
Members added
</p>
</div>
</div>
......
......@@ -7,9 +7,11 @@ 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_GROUP_NAME = 'Gitlab Org';
const TEST_MERGE_REQUESTS_COUNT = { data: { merge_requests_count: 10 } };
const TEST_ISSUES_COUNT = { data: { issues_count: 20 } };
const TEST_NEW_MEMBERS_COUNT = { data: { new_members_count: 30 } };
const REPORT_PAGES_PATH = 'report_pages';
describe('GroupActivity component', () => {
let wrapper;
......@@ -17,8 +19,11 @@ describe('GroupActivity component', () => {
const createComponent = () => {
wrapper = mount(GroupActivityCard, {
propsData: {
provide: {
groupFullPath: TEST_GROUP_ID,
groupName: TEST_GROUP_NAME,
reportPagesPath: REPORT_PAGES_PATH,
enableReportPages: false,
},
});
};
......@@ -85,9 +90,9 @@ describe('GroupActivity component', () => {
.then(waitForPromises)
.then(() => {
expect(findMetricCard().props('metrics')).toEqual([
{ key: 'mergeRequests', value: 10, label: 'Merge Requests created' },
{ key: 'issues', value: 20, label: 'Issues created' },
{ key: 'newMembers', value: 30, label: 'New Members created' },
{ key: 'mergeRequests', value: 10, label: 'Merge Requests opened', link: null },
{ key: 'issues', value: 20, label: 'Issues opened', link: null },
{ key: 'newMembers', value: 30, label: 'Members added', link: null },
]);
});
});
......
import ReportsApp from 'ee/analytics/reports/components/app.vue';
import { shallowMount } from '@vue/test-utils';
import { GlBreadcrumb } from '@gitlab/ui';
import { objectToQuery } from '~/lib/utils/url_utility';
const GROUP_NAME = 'Gitlab Org';
const GROUP_PATH = 'gitlab-org';
const DEFAULT_REPORT_TITLE = 'Report';
const GROUP_URL_QUERY = objectToQuery({
groupName: GROUP_NAME,
groupPath: GROUP_PATH,
});
describe('ReportsApp', () => {
let wrapper;
const createComponent = () => {
return shallowMount(ReportsApp);
};
const findGlBreadcrumb = () => wrapper.find(GlBreadcrumb);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('contains the correct breadcrumbs', () => {
it('displays the report title by default', () => {
wrapper = createComponent();
const breadcrumbs = findGlBreadcrumb();
expect(breadcrumbs.props('items')).toStrictEqual([{ text: DEFAULT_REPORT_TITLE, href: '' }]);
});
describe('with a group in the URL', () => {
beforeEach(() => {
window.history.replaceState({}, null, `?${GROUP_URL_QUERY}`);
});
it('displays the group name and report title', () => {
wrapper = createComponent();
const breadcrumbs = findGlBreadcrumb();
expect(breadcrumbs.props('items')).toStrictEqual([
{ text: GROUP_NAME, href: `/${GROUP_PATH}` },
{ text: DEFAULT_REPORT_TITLE, href: '' },
]);
});
});
});
});
......@@ -3,7 +3,7 @@ import { GlSkeletonLoading } from '@gitlab/ui';
import MetricCard from 'ee/analytics/shared/components/metric_card.vue';
const metrics = [
{ key: 'first_metric', value: 10, label: 'First metric', unit: 'days' },
{ key: 'first_metric', value: 10, label: 'First metric', unit: 'days', link: 'some_link' },
{ key: 'second_metric', value: 20, label: 'Yet another metric' },
{ key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' },
{ key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' },
......@@ -75,18 +75,23 @@ describe('MetricCard', () => {
});
describe.each`
columnIndex | label | value | unit
${0} | ${'First metric'} | ${10} | ${' days'}
${1} | ${'Yet another metric'} | ${20} | ${''}
${2} | ${'Null metric without value'} | ${'-'} | ${''}
${3} | ${'Metric without value'} | ${'-'} | ${''}
`('metric columns', ({ columnIndex, label, value, unit }) => {
it(`renders ${value}${unit} ${label}`, () => {
expect(
findMetricItem()
.at(columnIndex)
.text(),
).toEqual(`${value}${unit} ${label}`);
columnIndex | label | value | unit | link
${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
${1} | ${'Yet another metric'} | ${20} | ${''} | ${null}
${2} | ${'Null metric without value'} | ${'-'} | ${''} | ${null}
${3} | ${'Metric without value'} | ${'-'} | ${''} | ${null}
`('metric columns', ({ columnIndex, label, value, unit, link }) => {
it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
const allMetricItems = findMetricItem();
const metricItem = allMetricItems.at(columnIndex);
expect(metricItem.text()).toBe(`${value}${unit} ${label}`);
if (link) {
expect(metricItem.find('a').attributes('href')).toBe(link);
} else {
expect(metricItem.find('a').exists()).toBe(false);
}
});
});
});
......
......@@ -10280,6 +10280,9 @@ msgstr ""
msgid "Generate new export"
msgstr ""
msgid "GenericReports|Report"
msgstr ""
msgid "Geo"
msgstr ""
......@@ -11234,16 +11237,16 @@ msgstr ""
msgid "Group: %{name}"
msgstr ""
msgid "GroupActivityMetrics|New Members created"
msgid "GroupActivityMetrics|Issues opened"
msgstr ""
msgid "GroupActivyMetrics|Issues created"
msgid "GroupActivityMetrics|Members added"
msgstr ""
msgid "GroupActivyMetrics|Merge Requests created"
msgid "GroupActivityMetrics|Merge Requests opened"
msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)"
msgid "GroupActivityMetrics|Recent activity (last 90 days)"
msgstr ""
msgid "GroupImport|Failed to import group."
......
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