Commit eba2b726 authored by Scott Hampton's avatar Scott Hampton Committed by Phil Hughes

Sort and link projects in coverage table

- Sort the coverage data table by the most recent
updated report.
- Link the project in the table to the project report.
- Exclude the projects with no coverage from the table.
parent c46feaee
<script>
import Vue from 'vue';
import { GlCard, GlEmptyState, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import { GlCard, GlEmptyState, GlLink, GlSkeletonLoader, GlTable } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import SelectProjectsDropdown from './select_projects_dropdown.vue';
......@@ -12,6 +13,7 @@ export default {
components: {
GlCard,
GlEmptyState,
GlLink,
GlSkeletonLoader,
GlTable,
SelectProjectsDropdown,
......@@ -31,12 +33,16 @@ export default {
// fetch the same data more than once
this.allCoverageData = [
...this.allCoverageData,
...data.projects.nodes.map(project => ({
...project,
// if a project has no code coverage, set to default values
codeCoverageSummary:
project.codeCoverageSummary || this.$options.noCoverageDefaultSummary,
})),
// Remove the projects that don't have any code coverage
...data.projects.nodes
.filter(({ codeCoverageSummary }) => Boolean(codeCoverageSummary))
.map(project => ({
...project,
codeCoveragePath: joinPaths(
gon.relative_url_root || '',
`/${project.fullPath}/-/graphs/${project.repository.rootRef}/charts`,
),
})),
];
},
error() {
......@@ -76,6 +82,17 @@ export default {
selectedCoverageData() {
return this.allCoverageData.filter(({ id }) => this.projectIds[id]);
},
sortedCoverageData() {
// Sort the table by most recently updated coverage report
return [...this.selectedCoverageData].sort((a, b) => {
if (a.codeCoverageSummary.lastUpdatedAt > b.codeCoverageSummary.lastUpdatedAt) {
return -1;
} else if (a.codeCoverageSummary.lastUpdatedAt < b.codeCoverageSummary.lastUpdatedAt) {
return 1;
}
return 0;
});
},
},
methods: {
handleError() {
......@@ -127,11 +144,6 @@ export default {
'RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data.',
),
},
noCoverageDefaultSummary: {
averageCoverage: 0,
coverageCount: 0,
lastUpdatedAt: '', // empty string will default to "just now" in table
},
LOADING_STATE: {
rows: 4,
height: 10,
......@@ -183,7 +195,7 @@ export default {
data-testid="test-coverage-data-table"
thead-class="thead-white"
:fields="$options.tableFields"
:items="selectedCoverageData"
:items="sortedCoverageData"
>
<template #head(project)="data">
<div>{{ data.label }}</div>
......@@ -199,7 +211,9 @@ export default {
</template>
<template #cell(project)="{ item }">
<div :data-testid="`${item.id}-name`">{{ item.name }}</div>
<gl-link target="_blank" :href="item.codeCoveragePath" :data-testid="`${item.id}-name`">
{{ item.name }}
</gl-link>
</template>
<template #cell(averageCoverage)="{ item }">
<div :data-testid="`${item.id}-average`">
......
query getProjectsTestCoverage($projectIds: [ID!]) {
projects(ids: $projectIds) {
nodes {
fullPath
id
name
repository {
rootRef
}
codeCoverageSummary {
averageCoverage
coverageCount
......
......@@ -17,6 +17,7 @@ describe('Test coverage table component', () => {
const findEmptyState = () => wrapper.find('[data-testid="test-coverage-table-empty-state"]');
const findLoadingState = () => wrapper.find('[data-testid="test-coverage-loading-state"');
const findTable = () => wrapper.find('[data-testid="test-coverage-data-table"');
const findTableRows = () => findTable().findAll('tbody tr');
const findProjectNameById = id => wrapper.find(`[data-testid="${id}-name"`);
const findProjectAverageById = id => wrapper.find(`[data-testid="${id}-average"`);
const findProjectCountById = id => wrapper.find(`[data-testid="${id}-count"`);
......@@ -96,8 +97,10 @@ describe('Test coverage table component', () => {
describe('when code coverage is available', () => {
it('renders coverage table', () => {
const fullPath = 'gitlab-org/gitlab';
const id = 'gid://gitlab/Project/1';
const name = 'GitLab';
const rootRef = 'master';
const averageCoverage = '74.35';
const coverageCount = '5';
const yesterday = new Date();
......@@ -107,8 +110,13 @@ describe('Test coverage table component', () => {
data: {
allCoverageData: [
{
fullPath,
id,
name,
repository: {
rootRef,
},
codeCoveragePath: '#',
codeCoverageSummary: {
averageCoverage,
coverageCount,
......@@ -129,11 +137,103 @@ describe('Test coverage table component', () => {
expect(findProjectCountById(id).text()).toBe(coverageCount);
expect(findProjectDateById(id).text()).toBe('1 day ago');
});
it('sorts the table by the most recently updated report', () => {
const today = new Date();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const allCoverageData = [
{
fullPath: '-',
id: 1,
name: 'should be last',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedAt: yesterday.toISOString(),
},
},
{
fullPath: '-',
id: 2,
name: 'should be first',
repository: { rootRef: 'master' },
codeCoveragePath: '#',
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedAt: today.toISOString(),
},
},
];
createComponent({
data: {
allCoverageData,
projectIds: {
1: true,
2: true,
},
},
mountFn: mount,
});
expect(findTable().exists()).toBe(true);
expect(
findTableRows()
.at(0)
.text(),
).toContain('should be first');
expect(
findTableRows()
.at(1)
.text(),
).toContain('should be last');
});
it('renders the correct link', async () => {
const id = 1;
const fullPath = 'test/test';
const rootRef = 'master';
const expectedPath = `/${fullPath}/-/graphs/${rootRef}/charts`;
createComponentWithApollo({
data: {
projectIds: { [id]: true },
},
queryData: {
data: {
projects: {
nodes: [
{
fullPath,
name: 'test',
id,
repository: {
rootRef,
},
codeCoverageSummary: {
averageCoverage: '1.45',
coverageCount: '1',
lastUpdatedAt: new Date().toISOString(),
},
},
],
},
},
},
mountFn: mount,
});
jest.runOnlyPendingTimers();
await waitForPromises();
expect(findTable().exists()).toBe(true);
expect(findProjectNameById(id).attributes('href')).toBe(expectedPath);
});
});
describe('when selected project has no coverage', () => {
it('sets coverage to default values', async () => {
const name = 'test';
it('does not render the table', async () => {
const id = 1;
createComponentWithApollo({
data: {
......@@ -144,8 +244,12 @@ describe('Test coverage table component', () => {
projects: {
nodes: [
{
name,
fullPath: 'test/test',
name: 'test',
id,
repository: {
rootRef: 'master',
},
codeCoverageSummary: null,
},
],
......@@ -157,11 +261,7 @@ describe('Test coverage table component', () => {
jest.runOnlyPendingTimers();
await waitForPromises();
expect(findTable().exists()).toBe(true);
expect(findProjectNameById(id).text()).toBe(name);
expect(findProjectAverageById(id).text()).toBe('0.00%');
expect(findProjectCountById(id).text()).toBe('0');
expect(findProjectDateById(id).text()).toBe('just now');
expect(findTable().exists()).toBe(false);
});
});
});
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