Commit b6189c2d authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Natalia Tepluhina

Add report page FE skeleton

This commit introduces the FE skeleton code used for the
new reports page. It also include the first link into the page
as well as renames the group activity metric titles.
parent 21130cdf
...@@ -2,26 +2,27 @@ ...@@ -2,26 +2,27 @@
import Api from 'ee/api'; import Api from 'ee/api';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import MetricCard from '../../shared/components/metric_card.vue'; import MetricCard from '../../shared/components/metric_card.vue';
const ENABLED_REPORT_PAGES = ['mergeRequests'];
export default { export default {
name: 'GroupActivityCard', name: 'GroupActivityCard',
components: { components: {
MetricCard, MetricCard,
}, },
props: { inject: ['groupFullPath', 'groupName', 'reportPagesPath', 'enableReportPages'],
groupFullPath: {
type: String,
required: true,
},
},
data() { data() {
return { return {
isLoading: false, isLoading: false,
metrics: { metrics: {
mergeRequests: { value: null, label: s__('GroupActivyMetrics|Merge Requests created') }, mergeRequests: {
issues: { value: null, label: s__('GroupActivyMetrics|Issues created') }, value: null,
newMembers: { value: null, label: s__('GroupActivityMetrics|New Members created') }, 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 { ...@@ -33,6 +34,7 @@ export default {
key, key,
value, value,
label, label,
link: this.generateReportPageLink(key),
}; };
}); });
}, },
...@@ -60,13 +62,27 @@ export default { ...@@ -60,13 +62,27 @@ export default {
this.isLoading = false; 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> </script>
<template> <template>
<metric-card <metric-card
:title="s__('GroupActivyMetrics|Recent activity (last 90 days)')" :title="s__('GroupActivityMetrics|Recent activity (last 90 days)')"
:metrics="metricsArray" :metrics="metricsArray"
:is-loading="isLoading" :is-loading="isLoading"
/> />
......
...@@ -6,17 +6,20 @@ export default () => { ...@@ -6,17 +6,20 @@ export default () => {
if (!container) return; if (!container) return;
const { groupFullPath } = container.dataset; const { groupFullPath, groupName, reportPagesPath } = container.dataset;
const { reportPages: enableReportPages } = gon?.features;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
el: container, el: container,
provide: {
groupFullPath,
groupName,
reportPagesPath,
enableReportPages,
},
render(h) { render(h) {
return h(GroupActivityCard, { return h(GroupActivityCard);
props: {
groupFullPath,
},
});
}, },
}); });
}; };
<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> <script>
import { GlCard, GlSkeletonLoading } from '@gitlab/ui'; import { GlCard, GlSkeletonLoading, GlLink } from '@gitlab/ui';
export default { export default {
name: 'MetricCard', name: 'MetricCard',
components: { components: {
GlCard, GlCard,
GlSkeletonLoading, GlSkeletonLoading,
GlLink,
}, },
props: { props: {
title: { title: {
...@@ -45,7 +46,10 @@ export default { ...@@ -45,7 +46,10 @@ export default {
ref="metricItem" ref="metricItem"
class="js-metric-card-item flex-grow text-center" 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> <p class="text-secondary gl-font-sm mb-2">{{ metric.label }}</p>
</div> </div>
</div> </div>
......
import initReportsApp from 'ee/analytics/reports';
document.addEventListener('DOMContentLoaded', () => {
initReportsApp();
});
...@@ -13,6 +13,10 @@ module EE ...@@ -13,6 +13,10 @@ module EE
before_action only: :issues do before_action only: :issues do
push_frontend_feature_flag(:scoped_labels, @group) push_frontend_feature_flag(:scoped_labels, @group)
end end
before_action only: :show do
push_frontend_feature_flag(:report_pages)
end
end end
override :render_show_html override :render_show_html
......
- @hide_breadcrumbs = true
- page_title _("Reports") - page_title _("Reports")
#js-reports-app
- return unless show_group_activity_analytics? - 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`] = ` ...@@ -25,7 +25,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
4 4
</h3> </h3>
...@@ -40,7 +40,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -40,7 +40,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
- -
</h3> </h3>
...@@ -55,7 +55,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -55,7 +55,7 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
- -
</h3> </h3>
......
...@@ -25,7 +25,7 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -25,7 +25,7 @@ exports[`GroupActivity component matches the snapshot 1`] = `
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
10 10
</h3> </h3>
...@@ -33,14 +33,14 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -33,14 +33,14 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm mb-2"
> >
Merge Requests created Merge Requests opened
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
20 20
</h3> </h3>
...@@ -48,14 +48,14 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -48,14 +48,14 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm mb-2"
> >
Issues created Issues opened
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item flex-grow text-center"
> >
<h3 <h3
class="my-2" class="gl-my-2"
> >
30 30
</h3> </h3>
...@@ -63,7 +63,7 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -63,7 +63,7 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm mb-2"
> >
New Members created Members added
</p> </p>
</div> </div>
</div> </div>
......
...@@ -7,9 +7,11 @@ import MetricCard from 'ee/analytics/shared/components/metric_card.vue'; ...@@ -7,9 +7,11 @@ import MetricCard from 'ee/analytics/shared/components/metric_card.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
const TEST_GROUP_ID = 'gitlab-org'; const TEST_GROUP_ID = 'gitlab-org';
const TEST_GROUP_NAME = 'Gitlab Org';
const TEST_MERGE_REQUESTS_COUNT = { data: { merge_requests_count: 10 } }; const TEST_MERGE_REQUESTS_COUNT = { data: { merge_requests_count: 10 } };
const TEST_ISSUES_COUNT = { data: { issues_count: 20 } }; const TEST_ISSUES_COUNT = { data: { issues_count: 20 } };
const TEST_NEW_MEMBERS_COUNT = { data: { new_members_count: 30 } }; const TEST_NEW_MEMBERS_COUNT = { data: { new_members_count: 30 } };
const REPORT_PAGES_PATH = 'report_pages';
describe('GroupActivity component', () => { describe('GroupActivity component', () => {
let wrapper; let wrapper;
...@@ -17,8 +19,11 @@ describe('GroupActivity component', () => { ...@@ -17,8 +19,11 @@ describe('GroupActivity component', () => {
const createComponent = () => { const createComponent = () => {
wrapper = mount(GroupActivityCard, { wrapper = mount(GroupActivityCard, {
propsData: { provide: {
groupFullPath: TEST_GROUP_ID, groupFullPath: TEST_GROUP_ID,
groupName: TEST_GROUP_NAME,
reportPagesPath: REPORT_PAGES_PATH,
enableReportPages: false,
}, },
}); });
}; };
...@@ -85,9 +90,9 @@ describe('GroupActivity component', () => { ...@@ -85,9 +90,9 @@ describe('GroupActivity component', () => {
.then(waitForPromises) .then(waitForPromises)
.then(() => { .then(() => {
expect(findMetricCard().props('metrics')).toEqual([ expect(findMetricCard().props('metrics')).toEqual([
{ key: 'mergeRequests', value: 10, label: 'Merge Requests created' }, { key: 'mergeRequests', value: 10, label: 'Merge Requests opened', link: null },
{ key: 'issues', value: 20, label: 'Issues created' }, { key: 'issues', value: 20, label: 'Issues opened', link: null },
{ key: 'newMembers', value: 30, label: 'New Members created' }, { 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'; ...@@ -3,7 +3,7 @@ import { GlSkeletonLoading } from '@gitlab/ui';
import MetricCard from 'ee/analytics/shared/components/metric_card.vue'; import MetricCard from 'ee/analytics/shared/components/metric_card.vue';
const metrics = [ 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: 'second_metric', value: 20, label: 'Yet another metric' },
{ key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' }, { key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' },
{ key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' }, { key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' },
...@@ -75,18 +75,23 @@ describe('MetricCard', () => { ...@@ -75,18 +75,23 @@ describe('MetricCard', () => {
}); });
describe.each` describe.each`
columnIndex | label | value | unit columnIndex | label | value | unit | link
${0} | ${'First metric'} | ${10} | ${' days'} ${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
${1} | ${'Yet another metric'} | ${20} | ${''} ${1} | ${'Yet another metric'} | ${20} | ${''} | ${null}
${2} | ${'Null metric without value'} | ${'-'} | ${''} ${2} | ${'Null metric without value'} | ${'-'} | ${''} | ${null}
${3} | ${'Metric without value'} | ${'-'} | ${''} ${3} | ${'Metric without value'} | ${'-'} | ${''} | ${null}
`('metric columns', ({ columnIndex, label, value, unit }) => { `('metric columns', ({ columnIndex, label, value, unit, link }) => {
it(`renders ${value}${unit} ${label}`, () => { it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
expect( const allMetricItems = findMetricItem();
findMetricItem() const metricItem = allMetricItems.at(columnIndex);
.at(columnIndex)
.text(), expect(metricItem.text()).toBe(`${value}${unit} ${label}`);
).toEqual(`${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 "" ...@@ -10280,6 +10280,9 @@ msgstr ""
msgid "Generate new export" msgid "Generate new export"
msgstr "" msgstr ""
msgid "GenericReports|Report"
msgstr ""
msgid "Geo" msgid "Geo"
msgstr "" msgstr ""
...@@ -11231,16 +11234,16 @@ msgstr "" ...@@ -11231,16 +11234,16 @@ msgstr ""
msgid "Group: %{name}" msgid "Group: %{name}"
msgstr "" msgstr ""
msgid "GroupActivityMetrics|New Members created" msgid "GroupActivityMetrics|Issues opened"
msgstr "" msgstr ""
msgid "GroupActivyMetrics|Issues created" msgid "GroupActivityMetrics|Members added"
msgstr "" msgstr ""
msgid "GroupActivyMetrics|Merge Requests created" msgid "GroupActivityMetrics|Merge Requests opened"
msgstr "" msgstr ""
msgid "GroupActivyMetrics|Recent activity (last 90 days)" msgid "GroupActivityMetrics|Recent activity (last 90 days)"
msgstr "" msgstr ""
msgid "GroupImport|Failed to import group." 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