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 @@
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,
provide: {
groupFullPath,
groupName,
reportPagesPath,
enableReportPages,
},
render(h) {
return h(GroupActivityCard, {
props: {
groupFullPath,
},
});
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 ""
......@@ -11231,16 +11234,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