Commit 87145f1c authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Peter Hegman

Add sorting to DA overview table

This commit introduces sorting into the
DevOps Adoption overview table. The selected
sort value is persisted in lcoal cache.

Changelog: added
EE: true
parent 4cc57b8c
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
GlProgressBar, GlProgressBar,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { formatNumber } from '~/locale'; import { formatNumber } from '~/locale';
import { import {
TABLE_TEST_IDS_HEADERS, TABLE_TEST_IDS_HEADERS,
...@@ -20,14 +21,28 @@ import { ...@@ -20,14 +21,28 @@ import {
TABLE_TEST_IDS_ACTIONS, TABLE_TEST_IDS_ACTIONS,
TABLE_TEST_IDS_NAMESPACE, TABLE_TEST_IDS_NAMESPACE,
DEVOPS_ADOPTION_TABLE_CONFIGURATION, DEVOPS_ADOPTION_TABLE_CONFIGURATION,
OVERVIEW_TABLE_SORT_BY_STORAGE_KEY,
OVERVIEW_TABLE_SORT_DESC_STORAGE_KEY,
OVERVIEW_TABLE_NAME_KEY,
} from '../constants'; } from '../constants';
import DevopsAdoptionDeleteModal from './devops_adoption_delete_modal.vue'; import DevopsAdoptionDeleteModal from './devops_adoption_delete_modal.vue';
const thClass = ['gl-bg-white!', 'gl-text-gray-400']; const thClass = ['gl-bg-white!', 'gl-text-gray-400'];
const formatter = (value, key, item) => {
if (key === OVERVIEW_TABLE_NAME_KEY) {
return item.group?.namespace?.fullName;
} else if (item.adoption[key]) return item.adoption[key].adopted;
return 0;
};
const fieldOptions = { const fieldOptions = {
thClass, thClass,
thAttr: { 'data-testid': TABLE_TEST_IDS_HEADERS }, thAttr: { 'data-testid': TABLE_TEST_IDS_HEADERS },
sortable: true,
sortByFormatted: true,
formatter,
}; };
export default { export default {
...@@ -39,6 +54,7 @@ export default { ...@@ -39,6 +54,7 @@ export default {
GlBadge, GlBadge,
GlProgressBar, GlProgressBar,
DevopsAdoptionDeleteModal, DevopsAdoptionDeleteModal,
LocalStorageSync,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -54,6 +70,8 @@ export default { ...@@ -54,6 +70,8 @@ export default {
NAMESPACE: TABLE_TEST_IDS_NAMESPACE, NAMESPACE: TABLE_TEST_IDS_NAMESPACE,
}, },
cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION, cols: DEVOPS_ADOPTION_TABLE_CONFIGURATION,
sortByStorageKey: OVERVIEW_TABLE_SORT_BY_STORAGE_KEY,
sortDescStorageKey: OVERVIEW_TABLE_SORT_DESC_STORAGE_KEY,
props: { props: {
data: { data: {
type: Object, type: Object,
...@@ -63,6 +81,8 @@ export default { ...@@ -63,6 +81,8 @@ export default {
}, },
data() { data() {
return { return {
sortBy: OVERVIEW_TABLE_NAME_KEY,
sortDesc: false,
selectedNamespace: null, selectedNamespace: null,
deleteModalId: uniqueId('delete-modal-'), deleteModalId: uniqueId('delete-modal-'),
}; };
...@@ -74,7 +94,7 @@ export default { ...@@ -74,7 +94,7 @@ export default {
tableHeaderFields() { tableHeaderFields() {
return [ return [
{ {
key: 'name', key: OVERVIEW_TABLE_NAME_KEY,
label: I18N_GROUP_COL_LABEL, label: I18N_GROUP_COL_LABEL,
...fieldOptions, ...fieldOptions,
thClass: ['gl-w-grid-size-30', ...thClass], thClass: ['gl-w-grid-size-30', ...thClass],
...@@ -90,6 +110,7 @@ export default { ...@@ -90,6 +110,7 @@ export default {
key: 'actions', key: 'actions',
tdClass: 'actions-cell', tdClass: 'actions-cell',
...fieldOptions, ...fieldOptions,
sortable: false,
}, },
]; ];
}, },
...@@ -137,12 +158,16 @@ export default { ...@@ -137,12 +158,16 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<local-storage-sync v-model="sortBy" :storage-key="$options.sortByStorageKey" as-json />
<local-storage-sync v-model="sortDesc" :storage-key="$options.sortDescStorageKey" as-json />
<h4>{{ tableHeader }}</h4> <h4>{{ tableHeader }}</h4>
<gl-table <gl-table
:fields="tableHeaderFields" :fields="tableHeaderFields"
:items="formattedData" :items="formattedData"
thead-class="gl-border-t-0 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100" thead-class="gl-border-t-0 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
stacked="md" stacked="md"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
> >
<template v-for="header in tableHeaderFields" #[headerSlotName(header.key)]> <template v-for="header in tableHeaderFields" #[headerSlotName(header.key)]>
{{ header.label }} {{ header.label }}
......
...@@ -6,6 +6,7 @@ export const DEBOUNCE_DELAY = 500; ...@@ -6,6 +6,7 @@ export const DEBOUNCE_DELAY = 500;
export const PROGRESS_BAR_HEIGHT = '8px'; export const PROGRESS_BAR_HEIGHT = '8px';
export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM'; export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM';
export const OVERVIEW_TABLE_NAME_KEY = 'name';
export const TABLE_TEST_IDS_HEADERS = 'header'; export const TABLE_TEST_IDS_HEADERS = 'header';
export const TABLE_TEST_IDS_NAMESPACE = 'namespaceCol'; export const TABLE_TEST_IDS_NAMESPACE = 'namespaceCol';
export const TABLE_TEST_IDS_ACTIONS = 'actionsCol'; export const TABLE_TEST_IDS_ACTIONS = 'actionsCol';
...@@ -15,6 +16,9 @@ export const TABLE_TEST_IDS_LOCAL_STORAGE_SORT_DESC = 'localStorageSortDesc'; ...@@ -15,6 +16,9 @@ export const TABLE_TEST_IDS_LOCAL_STORAGE_SORT_DESC = 'localStorageSortDesc';
export const TABLE_SORT_BY_STORAGE_KEY = 'devops_adoption_table_sort_by'; export const TABLE_SORT_BY_STORAGE_KEY = 'devops_adoption_table_sort_by';
export const TABLE_SORT_DESC_STORAGE_KEY = 'devops_adoption_table_sort_desc'; export const TABLE_SORT_DESC_STORAGE_KEY = 'devops_adoption_table_sort_desc';
export const OVERVIEW_TABLE_SORT_BY_STORAGE_KEY = 'devops_adoption_overview_table_sort_by';
export const OVERVIEW_TABLE_SORT_DESC_STORAGE_KEY = 'devops_adoption_overview_table_sort_desc';
export const TRACK_ADOPTION_TAB_CLICK_EVENT = 'i_analytics_dev_ops_adoption'; export const TRACK_ADOPTION_TAB_CLICK_EVENT = 'i_analytics_dev_ops_adoption';
export const TRACK_DEVOPS_SCORE_TAB_CLICK_EVENT = 'i_analytics_dev_ops_score'; export const TRACK_DEVOPS_SCORE_TAB_CLICK_EVENT = 'i_analytics_dev_ops_score';
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
} from 'ee/analytics/devops_report/devops_adoption/constants'; } from 'ee/analytics/devops_report/devops_adoption/constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { devopsAdoptionNamespaceData } from '../mock_data'; import { devopsAdoptionNamespaceData } from '../mock_data';
const DELETE_MODAL_ID = 'delete-modal-test-unique-id'; const DELETE_MODAL_ID = 'delete-modal-test-unique-id';
...@@ -33,6 +34,10 @@ describe('DevopsAdoptionOverviewTable', () => { ...@@ -33,6 +34,10 @@ describe('DevopsAdoptionOverviewTable', () => {
}); });
}; };
beforeEach(() => {
localStorage.clear();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -46,6 +51,9 @@ describe('DevopsAdoptionOverviewTable', () => { ...@@ -46,6 +51,9 @@ describe('DevopsAdoptionOverviewTable', () => {
const findDeleteModal = () => wrapper.findComponent(DevopsAdoptionDeleteModal); const findDeleteModal = () => wrapper.findComponent(DevopsAdoptionDeleteModal);
const findSortByLocalStorageSync = () => wrapper.findAll(LocalStorageSync).at(0);
const findSortDescLocalStorageSync = () => wrapper.findAll(LocalStorageSync).at(1);
describe('table headings', () => { describe('table headings', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -54,9 +62,11 @@ describe('DevopsAdoptionOverviewTable', () => { ...@@ -54,9 +62,11 @@ describe('DevopsAdoptionOverviewTable', () => {
it('displays the table headings', () => { it('displays the table headings', () => {
const headerTexts = wrapper const headerTexts = wrapper
.findAllByTestId(TABLE_TEST_IDS_HEADERS) .findAllByTestId(TABLE_TEST_IDS_HEADERS)
.wrappers.map((x) => x.text()); .wrappers.map((x) => x.text().split('\n')[0]);
expect(headerTexts).toEqual(['Group', 'Dev', 'Sec', 'Ops', '']); headerTexts.pop(); // Remove the blank entry at the end used for the actions
expect(headerTexts).toEqual(['Group', 'Dev', 'Sec', 'Ops']);
}); });
}); });
...@@ -183,4 +193,45 @@ describe('DevopsAdoptionOverviewTable', () => { ...@@ -183,4 +193,45 @@ describe('DevopsAdoptionOverviewTable', () => {
}, },
); );
}); });
describe('sorting', () => {
let headers;
beforeEach(() => {
createComponent();
headers = wrapper.findAllByTestId(TABLE_TEST_IDS_HEADERS);
});
it.each`
column | index
${'name'} | ${0}
${'dev'} | ${1}
`('sorts correctly $column column', async ({ index }) => {
expect(findCol(TABLE_TEST_IDS_NAMESPACE).text()).toBe(
devopsAdoptionNamespaceData.nodes[0].namespace.fullName,
);
await headers.at(index).trigger('click');
expect(findCol(TABLE_TEST_IDS_NAMESPACE).text()).toBe(
devopsAdoptionNamespaceData.nodes[1].namespace.fullName,
);
});
it('should update local storage when the sort column changes', async () => {
expect(findSortByLocalStorageSync().props('value')).toBe('name');
await headers.at(1).trigger('click');
expect(findSortByLocalStorageSync().props('value')).toBe('dev');
});
it('should update local storage when the sort direction changes', async () => {
expect(findSortDescLocalStorageSync().props('value')).toBe(false);
await headers.at(0).trigger('click');
expect(findSortDescLocalStorageSync().props('value')).toBe(true);
});
});
}); });
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