Commit 356730c6 authored by Peter Hegman's avatar Peter Hegman

Merge branch '337666-devops-sorting-for-table-on-devops-adoption-overview-tab' into 'master'

[DevOps] Sorting for table on DevOps adoption Overview tab

See merge request gitlab-org/gitlab!70937
parents 2b719bdf 87145f1c
...@@ -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