From 477300a11bd8e57dc60d704bbbf89d0161078e49 Mon Sep 17 00:00:00 2001 From: Mick Ryan Date: Thu, 19 Dec 2019 15:17:11 -0800 Subject: [PATCH] feat(archive): Add sortable columns to ArchiveExplorer (#1126) * feat(archive): Add sortable columns to ArchiveExplorer --- src/lib/viewers/archive/ArchiveExplorer.js | 61 +++++++++++++--- .../__tests__/ArchiveExplorer-test-react.js | 73 +++++++++++++++++-- src/lib/viewers/archive/constants.js | 6 +- 3 files changed, 121 insertions(+), 19 deletions(-) diff --git a/src/lib/viewers/archive/ArchiveExplorer.js b/src/lib/viewers/archive/ArchiveExplorer.js index 685792ca1..e0ef00b18 100644 --- a/src/lib/viewers/archive/ArchiveExplorer.js +++ b/src/lib/viewers/archive/ArchiveExplorer.js @@ -6,13 +6,14 @@ import intlLocaleData from 'react-intl-locale-data'; // eslint-disable-line import Internationalize from 'box-ui-elements/es/elements/common/Internationalize'; import fuzzySearch from 'box-ui-elements/es/utils/fuzzySearch'; import { + itemNameCellRenderer, readableTimeCellRenderer, sizeCellRenderer, - itemNameCellRenderer, + sortableColumnHeaderRenderer, } from 'box-ui-elements/es/features/virtualized-table-renderers'; import VirtualizedTable from 'box-ui-elements/es/features/virtualized-table'; import { addLocaleData } from 'react-intl'; -import { Column } from 'react-virtualized/dist/es/Table/index'; +import { Column, SortDirection } from 'react-virtualized/dist/es/Table/index'; import Breadcrumbs from './Breadcrumbs'; import SearchBar from './SearchBar'; import { TABLE_COLUMNS, VIEWS } from './constants'; @@ -69,6 +70,8 @@ class ArchiveExplorer extends React.Component { this.state = { fullPath: props.itemCollection.find(info => !info.parent).absolute_path, searchQuery: '', + sortBy: '', + sortDirection: SortDirection.ASC, view: VIEW_FOLDER, }; } @@ -144,6 +147,16 @@ class ArchiveExplorer extends React.Component { view: query.trim() ? VIEW_SEARCH : VIEW_FOLDER, }); + /** + * Handle sort click + * + * @param {object} sort + * @param {string} sort.sortBy - Used to sort + * @param {string} sort.sortDirection - Set direction of sort either ASC | DESC + * @return {void} + */ + handleSort = ({ sortBy, sortDirection }) => this.setState({ sortBy, sortDirection }); + /** * Filter item collection for search query * @@ -156,6 +169,29 @@ class ArchiveExplorer extends React.Component { return itemCollection.filter(item => fuzzySearch(trimmedQuery, item.name, 0)); }; + /** + * Sort the item list depending on the key or direction + * @param {Array} itemList - Array of Item objects + * @return {Array} filtered items for search query + */ + sortItemList(itemList) { + const { sortBy, sortDirection } = this.state; + + if (!sortBy.length) { + return itemList; + } + + const sortedItems = itemList.sort((a, b) => { + if (typeof a[sortBy] === 'number' && typeof b[sortBy] === 'number') { + return a[sortBy] - b[sortBy]; + } + + return a[sortBy].localeCompare(b[sortBy]); + }); + + return sortDirection === SortDirection.ASC ? sortedItems : sortedItems.reverse(); + } + /** * render data * @@ -163,25 +199,32 @@ class ArchiveExplorer extends React.Component { */ render() { const { itemCollection } = this.props; - const { fullPath, searchQuery, view } = this.state; - const itemList = + const { fullPath, searchQuery, sortBy, sortDirection, view } = this.state; + const itemList = this.sortItemList( view === VIEW_SEARCH ? this.getSearchResult(itemCollection, searchQuery) - : this.getItemList(itemCollection, fullPath); + : this.getItemList(itemCollection, fullPath), + ); return (
- + {intl => [ , @@ -189,8 +232,8 @@ class ArchiveExplorer extends React.Component { key={KEY_MODIFIED_AT} cellRenderer={readableTimeCellRenderer} dataKey={KEY_MODIFIED_AT} - disableSort flexGrow={2} + headerRenderer={sortableColumnHeaderRenderer} label={__('last_modified_date')} width={1} />, @@ -198,8 +241,8 @@ class ArchiveExplorer extends React.Component { key={KEY_SIZE} cellRenderer={sizeCellRenderer()} dataKey={KEY_SIZE} - disableSort flexGrow={1} + headerRenderer={sortableColumnHeaderRenderer} label={__('size')} width={1} />, diff --git a/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js b/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js index 00c9fb56d..d1548b363 100644 --- a/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js +++ b/src/lib/viewers/archive/__tests__/ArchiveExplorer-test-react.js @@ -7,6 +7,8 @@ import { TABLE_COLUMNS, VIEWS } from '../constants'; const sandbox = sinon.sandbox.create(); let data; +const getComponent = props => shallow(); // eslint-disable-line + describe('lib/viewers/archive/ArchiveExplorer', () => { beforeEach(() => { data = [ @@ -69,7 +71,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('render()', () => { it('should render correct components', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); expect(component.find('.bp-ArchiveExplorer').length).to.equal(1); expect(component.find('SearchBar').length).to.equal(1); @@ -81,7 +83,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('handleItemClick()', () => { it('should set state when handleItemClick() is called', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); component.instance().handleItemClick({ fullPath: 'test/subfolder/' }); @@ -93,7 +95,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('handleBreadcrumbClick()', () => { it('should set state when handleBreadcrumbClick() is called', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); component.instance().handleBreadcrumbClick('test/subfolder/'); @@ -103,7 +105,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('getRowData()', () => { it('should return correct row data', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); const rowData = component.instance().getRowData(data)({ index: 0 }); @@ -126,7 +128,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('getItemList()', () => { it('should return correct item list', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); const itemList = component.instance().getItemList(data, 'test/'); @@ -136,7 +138,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('handleSearch()', () => { it('should set correct state when search query is not empty', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); component.instance().handleSearch('test'); expect(component.state().searchQuery).to.equal('test'); @@ -154,7 +156,7 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { describe('getSearchResult()', () => { it('should return correct item list', () => { - const component = shallow(); + const component = getComponent({ itemCollection: data }); const itemList = component.instance().getSearchResult(data, 'level-1'); const fuzzyList = component.instance().getSearchResult(data, 'leel1'); @@ -163,4 +165,61 @@ describe('lib/viewers/archive/ArchiveExplorer', () => { expect(fuzzyList).to.eql([data[1], data[2]]); }); }); + + describe('handleSort()', () => { + it('should set the sort direction and type', () => { + const component = getComponent({ itemCollection: data }); + const instance = component.instance(); + + instance.handleSort({ sortBy: 'name', sortDirection: 'DESC' }); + + expect(component.state().sortBy).to.equal('name'); + expect(component.state().sortDirection).to.equal('DESC'); + }); + }); + + describe('sortItemList()', () => { + it('should sort itemList by size and be in ASC order', () => { + const component = getComponent({ itemCollection: data }); + const instance = component.instance(); + const itemList = instance.getItemList(data, 'test/'); + + instance.handleSort({ sortBy: 'size', sortDirection: 'ASC' }); + const sortedList = instance.sortItemList(itemList); + + expect(sortedList[0]).to.equal(data[1]); + }); + + it('should sort itemList by name and be in DESC order', () => { + const component = getComponent({ itemCollection: data }); + const instance = component.instance(); + const itemList = instance.getItemList(data, 'test/'); + + instance.handleSort({ sortBy: 'name', sortDirection: 'DESC' }); + const sortedList = instance.sortItemList(itemList); + + expect(sortedList[0]).to.equal(data[2]); + }); + + it('should sort itemList by name and be in ASC order', () => { + const component = getComponent({ itemCollection: data }); + const instance = component.instance(); + const itemList = instance.getItemList(data, 'test/'); + + instance.handleSort({ sortBy: 'name', sortDirection: 'ASC' }); + const sortedList = instance.sortItemList(itemList); + + expect(sortedList[0]).to.equal(data[1]); + }); + + it('should not sort itemList', () => { + const component = getComponent({ itemCollection: data }); + const instance = component.instance(); + const itemList = instance.getItemList(data, 'test/'); + + const sortedList = instance.sortItemList(itemList); + + expect(sortedList[0]).to.equal(itemList[0]); + }); + }); }); diff --git a/src/lib/viewers/archive/constants.js b/src/lib/viewers/archive/constants.js index 2ad6b50d9..9b6d16efa 100644 --- a/src/lib/viewers/archive/constants.js +++ b/src/lib/viewers/archive/constants.js @@ -1,7 +1,7 @@ const TABLE_COLUMNS = { - KEY_MODIFIED_AT: 'key_modified_at', - KEY_NAME: 'key_name', - KEY_SIZE: 'key_size', + KEY_MODIFIED_AT: 'modified_at', + KEY_NAME: 'name', + KEY_SIZE: 'size', }; const VIEWS = {