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 {
this.allProjectsSelected = true;
this.selectedProjectIds = [];
},
selectProject(id) {
selectProject({ parsedId }) {
this.allProjectsSelected = false;
const index = this.selectedProjectIds.indexOf(id);
const index = this.selectedProjectIds.indexOf(parsedId);
if (index < 0) {
this.selectedProjectIds.push(id);
this.selectedProjectIds.push(parsedId);
return;
}
this.selectedProjectIds.splice(index, 1);
......
......@@ -43,7 +43,7 @@ export default {
update(data) {
return data.group.projects.nodes.map(project => ({
...project,
id: getIdFromGraphQLId(project.id),
parsedId: getIdFromGraphQLId(project.id),
isSelected: false,
}));
},
......@@ -87,7 +87,7 @@ export default {
const index = this.groupProjects.map(project => project.id).indexOf(id);
this.groupProjects[index].isSelected = !this.groupProjects[index].isSelected;
this.selectAllProjects = false;
this.$emit('select-project', id);
this.$emit('select-project', this.groupProjects[index]);
},
clickSelectAllProjects() {
this.selectAllProjects = true;
......@@ -95,7 +95,7 @@ export default {
...project,
isSelected: false,
}));
this.$emit('select-all-projects');
this.$emit('select-all-projects', this.groupProjects);
},
handleError() {
this.$emit('projects-query-error');
......
<script>
import { GlCard } from '@gitlab/ui';
import { s__ } from '~/locale';
import { GlCard, GlEmptyState, GlSkeletonLoader, GlTable } from '@gitlab/ui';
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 {
name: 'TestCoverageTable',
components: {
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: {
// This is a temporary placeholder until we actually implement the feature
header: s__('RepositoriesAnalytics|Test Code Coverage'),
emptyStateTitle: s__('RepositoriesAnalytics|Please select projects to display.'),
emptyStateDescription: s__(
'RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data.',
),
},
};
</script>
<template>
<gl-card>
<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 #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>
</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
Vue.use(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 () => {
const el = document.querySelector('#js-group-repository-analytics');
const { groupAnalyticsCoverageReportsPath, groupFullPath } = el?.dataset || {};
const { groupAnalyticsCoverageReportsPath, groupFullPath, coverageTableEmptyStateSvgPath } =
el?.dataset || {};
if (el) {
// eslint-disable-next-line no-new
......@@ -22,6 +37,7 @@ export default () => {
},
apolloProvider,
provide: {
coverageTableEmptyStateSvgPath,
groupAnalyticsCoverageReportsPath,
groupFullPath,
},
......
......@@ -5,4 +5,5 @@
= _("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"),
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', () => {
jest.spyOn(wrapper.vm, '$emit');
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', () => {
jest.spyOn(wrapper.vm, '$emit');
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 ""
msgid "Last Seen"
msgstr ""
msgid "Last Update"
msgstr ""
msgid "Last Used"
msgstr ""
......@@ -18054,6 +18057,9 @@ msgstr ""
msgid "Number of %{itemTitle}"
msgstr ""
msgid "Number of Coverages"
msgstr ""
msgid "Number of Elasticsearch replicas"
msgstr ""
......@@ -22031,6 +22037,12 @@ msgstr ""
msgid "RepositoriesAnalytics|Historic Test Coverage Data is available in raw format (.csv) for further analysis."
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"
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