From 7c7d5b49db653476f616dbda5727701958e05407 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 14:58:27 -0500 Subject: [PATCH 1/7] Move member/request list components out of "forms" directory --- js/components/{forms => }/members_list.js | 0 js/components/{forms => }/requests_list.js | 4 ++-- js/index.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename js/components/{forms => }/members_list.js (100%) rename js/components/{forms => }/requests_list.js (97%) diff --git a/js/components/forms/members_list.js b/js/components/members_list.js similarity index 100% rename from js/components/forms/members_list.js rename to js/components/members_list.js diff --git a/js/components/forms/requests_list.js b/js/components/requests_list.js similarity index 97% rename from js/components/forms/requests_list.js rename to js/components/requests_list.js index 1e3d586f..aafc234d 100644 --- a/js/components/forms/requests_list.js +++ b/js/components/requests_list.js @@ -1,5 +1,5 @@ -import LocalDatetime from '../../components/local_datetime' -import { formatDollars } from '../../lib/dollars' +import LocalDatetime from '../components/local_datetime' +import { formatDollars } from '../lib/dollars' import { parse } from 'date-fns' import { compose, partial, indexBy, prop, sortBy, reverse, pipe } from 'ramda' diff --git a/js/index.js b/js/index.js index 4edd4c08..01005a94 100644 --- a/js/index.js +++ b/js/index.js @@ -20,9 +20,9 @@ import selector from './components/selector' import BudgetChart from './components/charts/budget_chart' import SpendTable from './components/tables/spend_table' import CcpoApproval from './components/forms/ccpo_approval' -import MembersList from './components/forms/members_list' +import MembersList from './components/members_list' import LocalDatetime from './components/local_datetime' -import RequestsList from './components/forms/requests_list' +import RequestsList from './components/requests_list' Vue.config.productionTip = false From 01f588d54de7fe0a5cad0d84d4e7abc8e6621b17 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 15:42:33 -0500 Subject: [PATCH 2/7] Run tests in watch mode without cache Running in watch mode was not printing out console.log. https://github.com/facebook/jest/issues/2441 suggests setting --no-cache and that fixed it. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21cb6968..6dfc9d2a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "parcel build js/index.js -d static/assets --public-url /static/assets -o index.js", "test": "jest", "test:coverage": "jest --coverage", - "test:watch": "jest --watch" + "test:watch": "jest --watch --no-cache" }, "author": "", "license": "MIT", From d6d417e8f9bd7ab5dfcfaa9efd68e26b9191b673 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 16:02:56 -0500 Subject: [PATCH 3/7] Add default column sort on members list table --- js/components/__tests__/members_list.test.js | 54 ++++++++++++++++++++ js/components/members_list.js | 8 ++- 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 js/components/__tests__/members_list.test.js diff --git a/js/components/__tests__/members_list.test.js b/js/components/__tests__/members_list.test.js new file mode 100644 index 00000000..c14caf2a --- /dev/null +++ b/js/components/__tests__/members_list.test.js @@ -0,0 +1,54 @@ +import { shallowMount } from '@vue/test-utils' + +import MembersList from '../members_list' + +describe('MembersList', () => { + const members = [{ + name: 'Luke Skywalker', + num_env: 2, + status: 'active', + role: 'developer' + }, { + name: 'Chewie', + num_env: 3, + status: 'pending', + role: 'admin' + }] + const role_choices = [ + { display_name: 'Developer', name: 'developer' }, + { display_name: 'Admin', name: 'admin' }, + ] + const status_choices = [ + { display_name: 'Active', name: 'active' }, + { display_name: 'Pending', name: 'pending' }, + ] + + const createWrapper = () => shallowMount(MembersList, { + propsData: { + members, role_choices, status_choices + } + }) + + it('should sort by name by default', () => { + const wrapper = createWrapper() + const listedMembers = wrapper.vm.searchedList + const memberNames = listedMembers.map(member => member.name) + expect(memberNames).toEqual(['Chewie', 'Luke Skywalker']) + }) + + it('should reverse sort by name when updated with Name', () => { + const wrapper = createWrapper() + wrapper.vm.updateSort('Name') + const listedMembers = wrapper.vm.searchedList + const memberNames = listedMembers.map(member => member.name) + expect(memberNames).toEqual(['Luke Skywalker', 'Chewie']) + }) + + it('should sort by number of environments when environments selected', () => { + const wrapper = createWrapper() + wrapper.vm.updateSort('Environments') + const listedMembers = wrapper.vm.searchedList + const memberNames = listedMembers.map(member => member.name) + expect(memberNames).toEqual(['Luke Skywalker', 'Chewie']) + }) +}) diff --git a/js/components/members_list.js b/js/components/members_list.js index 7de99020..925895c6 100644 --- a/js/components/members_list.js +++ b/js/components/members_list.js @@ -90,6 +90,8 @@ export default { }, ] + const defaultSortColumn = 'Name' + return { searchValue: '', status: '', @@ -97,7 +99,7 @@ export default { role: '', rolesByDisplayName: indexBy(prop('display_name'), this.role_choices), sortInfo: { - columnName: '', + columnName: defaultSortColumn, isAscending: true, columns: indexBy(prop('displayName'), columns) }, @@ -127,5 +129,7 @@ export default { getColumns: function() { return Object.values(this.sortInfo.columns) } - } + }, + + template: '
' } From 334da1196b92fbf3124675433794c78cf19a731f Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 16:10:19 -0500 Subject: [PATCH 4/7] Fix indentation on requests-list component --- js/components/requests_list.js | 260 ++++++++++++++++----------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/js/components/requests_list.js b/js/components/requests_list.js index aafc234d..7f738ad4 100644 --- a/js/components/requests_list.js +++ b/js/components/requests_list.js @@ -4,145 +4,145 @@ import { parse } from 'date-fns' import { compose, partial, indexBy, prop, sortBy, reverse, pipe } from 'ramda' export default { - name: 'requests-list', + name: 'requests-list', - components: { - LocalDatetime, + components: { + LocalDatetime, + }, + + props: { + requests: { + type: Array, + default: [], }, + isExtended: { + type: Boolean, + default: false, + }, + statuses: { + type: Array, + default: [], + }, + dodComponents: { + type: Array, + default: [], + } + }, - props: { - requests: { - type: Array, - default: [], + data: function () { + const defaultSort = (sort, requests) => sortBy(prop(sort.columnName), requests) + const dateSort = (sort, requests) => { + const parseDate = compose(partial(parse), prop(sort.columnName)) + return sortBy(parseDate, requests) + } + + const columnList = [ + { + displayName: 'JEDI Cloud Request Name', + attr: 'name', + sortFunc: defaultSort, }, - isExtended: { - type: Boolean, - default: false, + { + displayName: 'Date Request Submitted', + attr: 'last_submission_timestamp', + sortFunc: dateSort, }, - statuses: { - type: Array, - default: [], + { + displayName: 'Date Request Last Edited', + attr: 'last_edited_timestamp', + extendedOnly: true, + sortFunc: dateSort, }, - dodComponents: { - type: Array, - default: [], + { + displayName: 'Requester', + attr: 'full_name', + extendedOnly: true, + sortFunc: defaultSort, + }, + { + displayName: 'Projected Annual Usage ($)', + attr: 'annual_usage', + sortFunc: defaultSort, + }, + { + displayName: 'Request Status', + attr: 'status', + sortFunc: defaultSort, + }, + { + displayName: 'DOD Component', + attr: 'dod_component', + extendedOnly: true, + sortFunc: defaultSort, + }, + ] + + return { + searchValue: '', + statusValue: '', + dodComponentValue: '', + sort: { + columnName: '', + isAscending: true + }, + columns: indexBy(prop('attr'), columnList), + } + }, + + computed: { + filteredRequests: function () { + return pipe( + partial(this.applySearch, [this.searchValue]), + partial(this.applyFilters, [this.statusValue, this.dodComponentValue]), + partial(this.applySort, [this.sort]), + )(this.requests) + } + }, + + methods: { + getColumns: function() { + return Object.values(this.columns) + .filter((column) => !column.extendedOnly || this.isExtended) + }, + applySearch: (query, requests) => { + return requests.filter( + (request) => query !== '' ? + request.name.toLowerCase().includes(query.toLowerCase()) : + true + ) + }, + applyFilters: (status, dodComponent, requests) => { + return requests.filter( + (request) => status !== '' ? + request.status === status : + true + ).filter( + (request) => dodComponent !== '' ? + request.dod_component === dodComponent : + true + ) + }, + applySort: function(sort, requests) { + if (sort.columnName === '') { + return requests + } else { + const { sortFunc } = this.columns[sort.columnName] + const sorted = sortFunc(sort, requests) + return sort.isAscending ? + sorted : + reverse(sorted) } }, + dollars: (value) => formatDollars(value, false), + updateSortValue: function(columnName) { + if (!this.isExtended) { return } - data: function () { - const defaultSort = (sort, requests) => sortBy(prop(sort.columnName), requests) - const dateSort = (sort, requests) => { - const parseDate = compose(partial(parse), prop(sort.columnName)) - return sortBy(parseDate, requests) + // toggle ascending / descending if column is clicked twice + if (columnName === this.sort.columnName) { + this.sort.isAscending = !this.sort.isAscending } - const columnList = [ - { - displayName: 'JEDI Cloud Request Name', - attr: 'name', - sortFunc: defaultSort, - }, - { - displayName: 'Date Request Submitted', - attr: 'last_submission_timestamp', - sortFunc: dateSort, - }, - { - displayName: 'Date Request Last Edited', - attr: 'last_edited_timestamp', - extendedOnly: true, - sortFunc: dateSort, - }, - { - displayName: 'Requester', - attr: 'full_name', - extendedOnly: true, - sortFunc: defaultSort, - }, - { - displayName: 'Projected Annual Usage ($)', - attr: 'annual_usage', - sortFunc: defaultSort, - }, - { - displayName: 'Request Status', - attr: 'status', - sortFunc: defaultSort, - }, - { - displayName: 'DOD Component', - attr: 'dod_component', - extendedOnly: true, - sortFunc: defaultSort, - }, - ] - - return { - searchValue: '', - statusValue: '', - dodComponentValue: '', - sort: { - columnName: '', - isAscending: true - }, - columns: indexBy(prop('attr'), columnList), - } + this.sort.columnName = columnName; }, - - computed: { - filteredRequests: function () { - return pipe( - partial(this.applySearch, [this.searchValue]), - partial(this.applyFilters, [this.statusValue, this.dodComponentValue]), - partial(this.applySort, [this.sort]), - )(this.requests) - } - }, - - methods: { - getColumns: function() { - return Object.values(this.columns) - .filter((column) => !column.extendedOnly || this.isExtended) - }, - applySearch: (query, requests) => { - return requests.filter( - (request) => query !== '' ? - request.name.toLowerCase().includes(query.toLowerCase()) : - true - ) - }, - applyFilters: (status, dodComponent, requests) => { - return requests.filter( - (request) => status !== '' ? - request.status === status : - true - ).filter( - (request) => dodComponent !== '' ? - request.dod_component === dodComponent : - true - ) - }, - applySort: function(sort, requests) { - if (sort.columnName === '') { - return requests - } else { - const { sortFunc } = this.columns[sort.columnName] - const sorted = sortFunc(sort, requests) - return sort.isAscending ? - sorted : - reverse(sorted) - } - }, - dollars: (value) => formatDollars(value, false), - updateSortValue: function(columnName) { - if (!this.isExtended) { return } - - // toggle ascending / descending if column is clicked twice - if (columnName === this.sort.columnName) { - this.sort.isAscending = !this.sort.isAscending - } - - this.sort.columnName = columnName; - }, - }, - } + }, +} From 87f144291492f89e1a5bcc40e5286cf7987888df Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 16:38:55 -0500 Subject: [PATCH 5/7] Use functions to create default array props --- js/components/requests_list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/components/requests_list.js b/js/components/requests_list.js index 7f738ad4..4b7b4540 100644 --- a/js/components/requests_list.js +++ b/js/components/requests_list.js @@ -13,7 +13,7 @@ export default { props: { requests: { type: Array, - default: [], + default: () => [], }, isExtended: { type: Boolean, @@ -21,11 +21,11 @@ export default { }, statuses: { type: Array, - default: [], + default: () => [], }, dodComponents: { type: Array, - default: [], + default: () => [], } }, From 6195b4369eabf43042b76be7f4c6e9ea79c2bcb2 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Tue, 27 Nov 2018 16:39:19 -0500 Subject: [PATCH 6/7] Add default sort by submission time to request listing --- js/components/__tests__/requests_list.test.js | 59 +++++++++++++++++++ js/components/requests_list.js | 7 ++- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 js/components/__tests__/requests_list.test.js diff --git a/js/components/__tests__/requests_list.test.js b/js/components/__tests__/requests_list.test.js new file mode 100644 index 00000000..e7d681bc --- /dev/null +++ b/js/components/__tests__/requests_list.test.js @@ -0,0 +1,59 @@ +import { shallowMount } from '@vue/test-utils' + +import RequestsList from '../requests_list' + +describe('RequestsList', () => { + + describe('isExtended', () => { + it('should disallow sorting if not extended', () => { + const wrapper = shallowMount(RequestsList, { propsData: { isExtended: false } }) + expect(wrapper.vm.sort.columnName).toEqual('') + wrapper.vm.updateSortValue('full_name') + expect(wrapper.vm.sort.columnName).toEqual('') + }) + + it('should allow sorting when in extended mode', () => { + const wrapper = shallowMount(RequestsList, { propsData: { isExtended: true } }) + expect(wrapper.vm.sort.columnName).toEqual('last_submission_timestamp') + wrapper.vm.updateSortValue('full_name') + expect(wrapper.vm.sort.columnName).toEqual('full_name') + }) + }) + + describe('sorting', () => { + const requests = [{ + name: 'X Wing', + last_edited_timestamp: 'Mon, 2 Jan 2017 12:34:56 GMT', + last_submission_timestamp: 'Mon, 2 Jan 2017 12:34:56 GMT', + full_name: 'Luke Skywalker', + annual_usage: '80000', + status: 'Approved', + dod_component: 'Rebels' + }, { + name: 'TIE Fighter', + last_edited_timestamp: 'Mon, 12 Nov 2018 12:34:56 GMT', + last_submission_timestamp: 'Mon, 12 Nov 2018 12:34:56 GMT', + full_name: 'Darth Vader', + annual_usage: '999999', + status: 'Approved', + dod_component: 'Empire' + }] + + const mountWrapper = () => shallowMount(RequestsList, { propsData: { requests, isExtended: true } }) + + it('should default to sorting by submission recency', () => { + const wrapper = mountWrapper() + const displayedRequests = wrapper.vm.filteredRequests + const requestNames = displayedRequests.map(req => req.name) + expect(requestNames).toEqual(['TIE Fighter', 'X Wing']) + }) + + it('should reverse sort by submission time when selected', () => { + const wrapper = mountWrapper() + wrapper.vm.updateSortValue('last_submission_timestamp') + const displayedRequests = wrapper.vm.filteredRequests + const requestNames = displayedRequests.map(req => req.name) + expect(requestNames).toEqual(['X Wing', 'TIE Fighter']) + }) + }) +}) diff --git a/js/components/requests_list.js b/js/components/requests_list.js index 4b7b4540..ec9f9282 100644 --- a/js/components/requests_list.js +++ b/js/components/requests_list.js @@ -77,13 +77,14 @@ export default { }, ] + const defaultSortColumn = this.isExtended ? 'last_submission_timestamp' : '' return { searchValue: '', statusValue: '', dodComponentValue: '', sort: { - columnName: '', - isAscending: true + columnName: defaultSortColumn, + isAscending: false }, columns: indexBy(prop('attr'), columnList), } @@ -145,4 +146,6 @@ export default { this.sort.columnName = columnName; }, }, + + template: '
' } From 30aa5f375b3f9fd08530ab51f786a40201278afb Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 28 Nov 2018 11:46:54 -0500 Subject: [PATCH 7/7] Clarify testing of sorting by number of envs --- js/components/__tests__/members_list.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/components/__tests__/members_list.test.js b/js/components/__tests__/members_list.test.js index c14caf2a..a386c5ce 100644 --- a/js/components/__tests__/members_list.test.js +++ b/js/components/__tests__/members_list.test.js @@ -48,7 +48,7 @@ describe('MembersList', () => { const wrapper = createWrapper() wrapper.vm.updateSort('Environments') const listedMembers = wrapper.vm.searchedList - const memberNames = listedMembers.map(member => member.name) - expect(memberNames).toEqual(['Luke Skywalker', 'Chewie']) + const memberEnvs = listedMembers.map(member => member.num_env) + expect(memberEnvs).toEqual([2, 3]) }) })