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