Commit 1069a821 authored by Scott Hampton's avatar Scott Hampton

Create projects coverage table

Add the table for displaying the test coverage
for selected projects from a group. This uses
a temporary client resolver while we wait for
the backend work to be finished.
parent 9aae3376
...@@ -100,11 +100,11 @@ export default { ...@@ -100,11 +100,11 @@ export default {
this.allProjectsSelected = true; this.allProjectsSelected = true;
this.selectedProjectIds = []; this.selectedProjectIds = [];
}, },
selectProject(id) { selectProject({ parsedId }) {
this.allProjectsSelected = false; this.allProjectsSelected = false;
const index = this.selectedProjectIds.indexOf(id); const index = this.selectedProjectIds.indexOf(parsedId);
if (index < 0) { if (index < 0) {
this.selectedProjectIds.push(id); this.selectedProjectIds.push(parsedId);
return; return;
} }
this.selectedProjectIds.splice(index, 1); this.selectedProjectIds.splice(index, 1);
......
...@@ -43,7 +43,7 @@ export default { ...@@ -43,7 +43,7 @@ export default {
update(data) { update(data) {
return data.group.projects.nodes.map(project => ({ return data.group.projects.nodes.map(project => ({
...project, ...project,
id: getIdFromGraphQLId(project.id), parsedId: getIdFromGraphQLId(project.id),
isSelected: false, isSelected: false,
})); }));
}, },
...@@ -87,7 +87,7 @@ export default { ...@@ -87,7 +87,7 @@ export default {
const index = this.groupProjects.map(project => project.id).indexOf(id); const index = this.groupProjects.map(project => project.id).indexOf(id);
this.groupProjects[index].isSelected = !this.groupProjects[index].isSelected; this.groupProjects[index].isSelected = !this.groupProjects[index].isSelected;
this.selectAllProjects = false; this.selectAllProjects = false;
this.$emit('select-project', id); this.$emit('select-project', this.groupProjects[index]);
}, },
clickSelectAllProjects() { clickSelectAllProjects() {
this.selectAllProjects = true; this.selectAllProjects = true;
...@@ -95,7 +95,7 @@ export default { ...@@ -95,7 +95,7 @@ export default {
...project, ...project,
isSelected: false, isSelected: false,
})); }));
this.$emit('select-all-projects'); this.$emit('select-all-projects', this.groupProjects);
}, },
handleError() { handleError() {
this.$emit('projects-query-error'); this.$emit('projects-query-error');
......
<script> <script>
import { GlCard } from '@gitlab/ui'; import { GlCard, GlEmptyState, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import { s__ } from '~/locale'; import { __, s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import SelectProjectsDropdown from './select_projects_dropdown.vue';
import getProjectsTestCoverage from '../graphql/queries/get_projects_test_coverage.query.graphql';
export default { export default {
name: 'TestCoverageTable', name: 'TestCoverageTable',
components: { components: {
GlCard, GlCard,
GlEmptyState,
GlSkeletonLoader,
GlTable,
SelectProjectsDropdown,
TimeAgoTooltip,
}, },
inject: {
coverageTableEmptyStateSvgPath: {
type: String,
default: '',
},
},
data() {
return {
coverageData: [],
hasError: false,
allProjectsSelected: false,
selectedProjectIds: [],
isLoading: false,
};
},
computed: {
hasCoverageData() {
return this.coverageData.length;
},
},
methods: {
getCoverageData() {
this.$apollo.addSmartQuery('coverageData', {
query: getProjectsTestCoverage,
debounce: 500,
variables() {
return {
projectIds: this.selectedProjectIds,
};
},
update(data) {
return data.projects.nodes;
},
error() {
this.handleError();
},
watchLoading(isLoading) {
this.isLoading = isLoading;
},
});
},
handleError() {
this.hasError = true;
},
selectAllProjects(allProjects) {
this.selectedProjectIds = allProjects.map(project => project.id);
this.allProjectsSelected = true;
this.getCoverageData();
},
selectProject({ id }) {
if (this.allProjectsSelected) {
this.allProjectsSelected = false;
this.selectedProjectIds = [id];
this.getCoverageData();
return;
}
const index = this.selectedProjectIds.indexOf(id);
if (index < 0) {
this.selectedProjectIds.push(id);
this.getCoverageData();
return;
}
this.selectedProjectIds.splice(index, 1);
this.getCoverageData();
},
},
tableFields: [
{
key: 'project',
label: __('Project'),
},
{
key: 'coverage',
label: __('Coverage'),
},
{
key: 'numberOfCoverages',
label: __('Number of Coverages'),
},
{
key: 'lastUpdate',
label: __('Last Update'),
},
],
text: { text: {
// This is a temporary placeholder until we actually implement the feature emptyStateTitle: s__('RepositoriesAnalytics|Please select projects to display.'),
header: s__('RepositoriesAnalytics|Test Code Coverage'), emptyStateDescription: s__(
'RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data.',
),
}, },
}; };
</script> </script>
<template> <template>
<gl-card> <gl-card>
<template #header> <template #header>
<h5>{{ $options.text.header }}</h5> <select-projects-dropdown
class="gl-w-quarter"
@projects-query-error="handleError"
@select-all-projects="selectAllProjects"
@select-project="selectProject"
/>
</template>
<gl-skeleton-loader v-if="isLoading" :width="430" :height="55">
<rect width="90" height="10" x="0" y="0" rx="4" />
<rect width="80" height="10" x="95" y="0" rx="4" />
<rect width="145" height="10" x="180" y="0" rx="4" />
<rect width="100" height="10" x="330" y="0" rx="4" />
<rect width="90" height="10" x="0" y="15" rx="4" />
<rect width="80" height="10" x="95" y="15" rx="4" />
<rect width="145" height="10" x="180" y="15" rx="4" />
<rect width="100" height="10" x="330" y="15" rx="4" />
<rect width="90" height="10" x="0" y="30" rx="4" />
<rect width="80" height="10" x="95" y="30" rx="4" />
<rect width="145" height="10" x="180" y="30" rx="4" />
<rect width="100" height="10" x="330" y="30" rx="4" />
<rect width="90" height="10" x="0" y="45" rx="4" />
<rect width="80" height="10" x="95" y="45" rx="4" />
<rect width="145" height="10" x="180" y="45" rx="4" />
<rect width="100" height="10" x="330" y="45" rx="4" />
</gl-skeleton-loader>
<gl-table
v-else-if="hasCoverageData"
data-testid="coverage-data-table"
thead-class="thead-white"
:fields="$options.tableFields"
:items="coverageData"
>
<template #head(project)="data">
<div>{{ data.label }}</div>
</template>
<template #head(coverage)="data">
<div>{{ data.label }}</div>
</template> </template>
<template #head(numberOfCoverages)="data">
<div>{{ data.label }}</div>
</template>
<template #head(lastUpdate)="data">
<div>{{ data.label }}</div>
</template>
<template #cell(project)="{ item }">
<div>{{ item.name }}</div>
</template>
<template #cell(coverage)="{ item }">
<div>{{ item.codeCoverage.average }}%</div>
</template>
<template #cell(numberOfCoverages)="{ item }">
<div>{{ item.codeCoverage.count }}</div>
</template>
<template #cell(lastUpdate)="{ item }">
<time-ago-tooltip :time="item.codeCoverage.lastUpdatedAt" />
</template>
</gl-table>
<gl-empty-state
v-else
:svg-path="coverageTableEmptyStateSvgPath"
:title="$options.text.emptyStateTitle"
:description="$options.text.emptyStateDescription"
/>
</gl-card> </gl-card>
</template> </template>
query getProjectsTestCoverage($projectIds: [ID!]) {
projects(ids: $projectIds) {
nodes {
id
name
codeCoverage @client {
average
count
lastUpdatedAt
}
}
}
}
...@@ -6,12 +6,27 @@ import GroupRepositoryAnalytics from './components/group_repository_analytics.vu ...@@ -6,12 +6,27 @@ import GroupRepositoryAnalytics from './components/group_repository_analytics.vu
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient({
Project: {
/*
The backend for adding `codeCoverage` the API is being worked on in parallel.
This is a temporary client resolver for this data. This feature is behind
a feature flag (:group_coverage_data_report)
*/
codeCoverage: () => ({
average: (Math.random() * 100).toFixed(2),
count: Math.ceil(Math.random() * Math.floor(10)), // random number between 1 and 10
lastUpdatedAt: '2020-09-29T21:42:00Z',
__typename: 'CodeCoverage',
}),
},
}),
}); });
export default () => { export default () => {
const el = document.querySelector('#js-group-repository-analytics'); const el = document.querySelector('#js-group-repository-analytics');
const { groupAnalyticsCoverageReportsPath, groupFullPath } = el?.dataset || {}; const { groupAnalyticsCoverageReportsPath, groupFullPath, coverageTableEmptyStateSvgPath } =
el?.dataset || {};
if (el) { if (el) {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -22,6 +37,7 @@ export default () => { ...@@ -22,6 +37,7 @@ export default () => {
}, },
apolloProvider, apolloProvider,
provide: { provide: {
coverageTableEmptyStateSvgPath,
groupAnalyticsCoverageReportsPath, groupAnalyticsCoverageReportsPath,
groupFullPath, groupFullPath,
}, },
......
...@@ -5,4 +5,5 @@ ...@@ -5,4 +5,5 @@
= _("Repositories Analytics") = _("Repositories Analytics")
#js-group-repository-analytics{ data: { group_analytics_coverage_reports_path: group_analytics_coverage_reports_path(@group, format: :csv, ref_path: "refs/heads/master"), #js-group-repository-analytics{ data: { group_analytics_coverage_reports_path: group_analytics_coverage_reports_path(@group, format: :csv, ref_path: "refs/heads/master"),
group_full_path: @group.full_path } } group_full_path: @group.full_path,
coverage_table_empty_state_svg_path: image_path('illustrations/chart-empty-state.svg') } }
...@@ -79,7 +79,9 @@ describe('Select projects dropdown component', () => { ...@@ -79,7 +79,9 @@ describe('Select projects dropdown component', () => {
jest.spyOn(wrapper.vm, '$emit'); jest.spyOn(wrapper.vm, '$emit');
selectAllProjects(); selectAllProjects();
expect(wrapper.vm.$emit).toHaveBeenCalledWith('select-all-projects'); expect(wrapper.vm.$emit).toHaveBeenCalledWith('select-all-projects', [
{ id: 1, name: '1', isSelected: false },
]);
}); });
}); });
...@@ -118,7 +120,11 @@ describe('Select projects dropdown component', () => { ...@@ -118,7 +120,11 @@ describe('Select projects dropdown component', () => {
jest.spyOn(wrapper.vm, '$emit'); jest.spyOn(wrapper.vm, '$emit');
selectProjectById(1); selectProjectById(1);
expect(wrapper.vm.$emit).toHaveBeenCalledWith('select-project', 1); expect(wrapper.vm.$emit).toHaveBeenCalledWith('select-project', {
id: 1,
name: '1',
isSelected: true,
});
}); });
}); });
......
...@@ -15064,6 +15064,9 @@ msgstr "" ...@@ -15064,6 +15064,9 @@ msgstr ""
msgid "Last Seen" msgid "Last Seen"
msgstr "" msgstr ""
msgid "Last Update"
msgstr ""
msgid "Last Used" msgid "Last Used"
msgstr "" msgstr ""
...@@ -18054,6 +18057,9 @@ msgstr "" ...@@ -18054,6 +18057,9 @@ msgstr ""
msgid "Number of %{itemTitle}" msgid "Number of %{itemTitle}"
msgstr "" msgstr ""
msgid "Number of Coverages"
msgstr ""
msgid "Number of Elasticsearch replicas" msgid "Number of Elasticsearch replicas"
msgstr "" msgstr ""
...@@ -22031,6 +22037,12 @@ msgstr "" ...@@ -22031,6 +22037,12 @@ msgstr ""
msgid "RepositoriesAnalytics|Historic Test Coverage Data is available in raw format (.csv) for further analysis." msgid "RepositoriesAnalytics|Historic Test Coverage Data is available in raw format (.csv) for further analysis."
msgstr "" msgstr ""
msgid "RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data."
msgstr ""
msgid "RepositoriesAnalytics|Please select projects to display."
msgstr ""
msgid "RepositoriesAnalytics|Test Code Coverage" msgid "RepositoriesAnalytics|Test Code Coverage"
msgstr "" 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