Skip to content

Commit 2eb03c5

Browse files
Sort Notifications recipients (#1665)
https://eaflood.atlassian.net/browse/WATER-4775 This change introduces a sort on the recipients array in the presenter. It sorts by the contacts name. We need to work out the name before we can sort. So we first map the recipients and then sort. This will result in all the recipients being mapped and sorted, with only 25 (the current default pagination per page) of the results being used. We have deemed this acceptable for the sake of simplicity and have accounted for performance concerns.
1 parent fe52ea6 commit 2eb03c5

File tree

4 files changed

+139
-56
lines changed

4 files changed

+139
-56
lines changed

app/presenters/notifications/setup/review.presenter.js

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const { defaultPageSize } = require('../../../../config/database.config.js')
1313
*
1414
* @param {object[]} recipients - List of recipient objects, each containing recipient details like email or name.
1515
* @param {number|string} page - The currently selected page
16-
* @param {object} pagination -
16+
* @param {object} pagination - The result from `PaginatorPresenter`
1717
*
1818
* @returns {object} - The data formatted for the view template
1919
*/
@@ -29,13 +29,10 @@ function go(recipients, page, pagination) {
2929
/**
3030
* Contact can be an email or an address (letter)
3131
*
32-
* If it is an address then we convert the contact CSV string to an array
32+
* If it is an address then we convert the contact CSV string to an array. If it is an email we return the email in
33+
* array for the UI to have consistent formatting.
3334
*
34-
* If it is an email we return the email in array for the UI to have consistent formatting
35-
*
36-
* @param {object} recipient
37-
*
38-
* @returns {string[]}
35+
* @private
3936
*/
4037
function _contact(recipient) {
4138
if (recipient.email) {
@@ -44,17 +41,18 @@ function _contact(recipient) {
4441

4542
const name = contactName(recipient.contact)
4643
const address = contactAddress(recipient.contact)
44+
4745
return [name, ...address]
4846
}
4947

50-
/**
51-
* Convert the licence CSV string to an array
52-
*
53-
* @param {string} licences
54-
* @returns {string[]}
55-
*/
56-
function _licences(licences) {
57-
return licences.split(',')
48+
function _formatRecipients(recipients) {
49+
return recipients.map((recipient) => {
50+
return {
51+
contact: _contact(recipient),
52+
licences: recipient.licence_refs.split(','),
53+
method: `${recipient.message_type} - ${recipient.contact_type}`
54+
}
55+
})
5856
}
5957

6058
function _pageTitle(page, pagination) {
@@ -66,24 +64,55 @@ function _pageTitle(page, pagination) {
6664
}
6765

6866
/**
69-
* Due to the complexity of the query to get the recipients data, we handle pagination in the presenter.
67+
* Due to the complexity of the query we had to use a raw query to get the recipients data. This means we need to handle
68+
* pagination (which recipients to display based on selected page and page size) in here.
7069
*
7170
* @private
7271
*/
73-
function _pagination(recipients, page) {
72+
function _paginateRecipients(recipients, page) {
7473
const pageNumber = Number(page) * defaultPageSize
74+
7575
return recipients.slice(pageNumber - defaultPageSize, pageNumber)
7676
}
7777

78+
/**
79+
* Sorts, maps, and paginates the recipients list.
80+
*
81+
* This function first maps over the recipients to transform each recipient object into a new format, then sorts the
82+
* resulting array of transformed recipients alphabetically by their contact's name. After sorting, it uses pagination
83+
* to return only the relevant subset of recipients for the given page.
84+
*
85+
* The map and sort are performed before pagination, as it is necessary to have the recipients in a defined order before
86+
* determining which recipients should appear on the page.
87+
*
88+
* @private
89+
*/
7890
function _recipients(recipients, page) {
79-
const paginatedRecipients = _pagination(recipients, page)
91+
const formattedRecipients = _formatRecipients(recipients)
92+
const sortedRecipients = _sortRecipients(formattedRecipients)
8093

81-
return paginatedRecipients.map((recipient) => {
82-
return {
83-
contact: _contact(recipient),
84-
licences: _licences(recipient.licence_refs),
85-
method: `${recipient.message_type} - ${recipient.contact_type}`
94+
return _paginateRecipients(sortedRecipients, page)
95+
}
96+
97+
/**
98+
* Sorts the recipients alphabetically by their 'contact name'.
99+
*
100+
* The contact name is the first element in the recipient's `contact` array. For letter-based recipients this will
101+
* be either the person or organisation name, and for email recipients this will be the email address.
102+
*
103+
* @private
104+
*/
105+
function _sortRecipients(recipients) {
106+
return recipients.sort((a, b) => {
107+
if (a.contact[0] < b.contact[0]) {
108+
return -1
109+
}
110+
111+
if (a.contact[0] > b.contact[0]) {
112+
return 1
86113
}
114+
115+
return 0
87116
})
88117
}
89118

test/fixtures/recipients.fixtures.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ function recipients() {
2525
*/
2626
function duplicateRecipients() {
2727
const duplicateLicenceRef = generateLicenceRef()
28+
const licenceDuplicateLicenceRef = generateLicenceRef()
29+
2830
return {
29-
duplicateLicenceHolder: _addDuplicateLicenceHolder(duplicateLicenceRef),
30-
duplicateReturnsTo: _addDuplicateReturnsTo(duplicateLicenceRef),
31+
duplicateLicenceHolder: _addDuplicateLicenceHolder(licenceDuplicateLicenceRef),
32+
duplicateReturnsTo: _addDuplicateReturnsTo(licenceDuplicateLicenceRef),
3133
duplicatePrimaryUser: _addDuplicatePrimaryUser(duplicateLicenceRef),
3234
duplicateReturnsAgent: _addDuplicateReturnsAgent(duplicateLicenceRef)
3335
}
@@ -45,7 +47,7 @@ function _addDuplicateLicenceHolder(licenceRef) {
4547
function _addDuplicateReturnsTo(licenceRef) {
4648
return {
4749
licence_refs: licenceRef,
48-
contact_type: 'Returns To',
50+
contact_type: 'Returns to',
4951
contact: _contact('4', 'Duplicate Returns to', 'Returns to'),
5052
contact_hash_id: 'b1b355491c7d42778890c545e08797ea'
5153
}
@@ -96,7 +98,7 @@ function _addDuplicateReturnsAgent(licenceRef) {
9698
contact: null,
9799
contact_hash_id: '2e6918568dfbc1d78e2fbe279fftt990',
98100
contact_type: 'Returns agent',
99-
recipient: '[email protected]'
101+
100102
}
101103
}
102104

test/presenters/notifications/setup/review.presenter.test.js

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ describe('Notifications Setup - Review presenter', () => {
1818
let pagination
1919
let testInput
2020
let testRecipients
21+
let testDuplicateRecipients
2122

2223
beforeEach(() => {
2324
page = 1
@@ -26,8 +27,12 @@ describe('Notifications Setup - Review presenter', () => {
2627
}
2728

2829
testRecipients = RecipientsFixture.recipients()
30+
// This data is used to ensure the recipients are grouped when they have the same licence ref / name.
31+
// Ignore the fact that these would be considered duplicates elsewhere in the code
32+
// (e.g. contact hash / address being identical)
33+
testDuplicateRecipients = RecipientsFixture.duplicateRecipients()
2934

30-
testInput = Object.values(testRecipients).map((recipient) => {
35+
testInput = [...Object.values(testRecipients), ...Object.values(testDuplicateRecipients)].map((recipient) => {
3136
return {
3237
...recipient,
3338
// The determine recipients service will add the message_type relevant to the recipient
@@ -39,33 +44,28 @@ describe('Notifications Setup - Review presenter', () => {
3944
})
4045

4146
describe('when provided with "recipients"', () => {
42-
it('correctly presents the data', () => {
47+
it('correctly presents the data (in alphabetical order)', () => {
4348
const result = ReviewPresenter.go(testInput, page, pagination)
4449

4550
expect(result).to.equal({
4651
defaultPageSize: 25,
4752
pageTitle: 'Send returns invitations',
4853
recipients: [
4954
{
50-
contact: ['[email protected]'],
51-
licences: [testRecipients.primaryUser.licence_refs],
52-
method: 'Letter or email - Primary user'
55+
contact: ['Mr H J Duplicate Licence holder', '4', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
56+
licences: [testDuplicateRecipients.duplicateLicenceHolder.licence_refs],
57+
method: 'Letter or email - Licence holder'
5358
},
5459
{
55-
contact: ['[email protected]'],
56-
licences: [testRecipients.returnsAgent.licence_refs],
57-
method: 'Letter or email - Returns agent'
60+
contact: ['Mr H J Duplicate Returns to', '4', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
61+
licences: [testDuplicateRecipients.duplicateReturnsTo.licence_refs],
62+
method: 'Letter or email - Returns to'
5863
},
5964
{
6065
contact: ['Mr H J Licence holder', '1', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
6166
licences: [testRecipients.licenceHolder.licence_refs],
6267
method: 'Letter or email - Licence holder'
6368
},
64-
{
65-
contact: ['Mr H J Returns to', '2', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
66-
licences: [testRecipients.returnsTo.licence_refs],
67-
method: 'Letter or email - Returns to'
68-
},
6969
{
7070
contact: [
7171
'Mr H J Licence holder with multiple licences',
@@ -77,9 +77,34 @@ describe('Notifications Setup - Review presenter', () => {
7777
],
7878
licences: testRecipients.licenceHolderWithMultipleLicences.licence_refs.split(','),
7979
method: 'Letter or email - Licence holder'
80+
},
81+
{
82+
contact: ['Mr H J Returns to', '2', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
83+
licences: [testRecipients.returnsTo.licence_refs],
84+
method: 'Letter or email - Returns to'
85+
},
86+
{
87+
contact: ['[email protected]'],
88+
licences: [testRecipients.primaryUser.licence_refs],
89+
method: 'Letter or email - Primary user'
90+
},
91+
{
92+
contact: ['[email protected]'],
93+
licences: [testDuplicateRecipients.duplicatePrimaryUser.licence_refs],
94+
method: 'Letter or email - Primary user'
95+
},
96+
{
97+
contact: ['[email protected]'],
98+
licences: [testRecipients.returnsAgent.licence_refs],
99+
method: 'Letter or email - Returns agent'
100+
},
101+
{
102+
contact: ['[email protected]'],
103+
licences: [testDuplicateRecipients.duplicateReturnsAgent.licence_refs],
104+
method: 'Letter or email - Returns agent'
80105
}
81106
],
82-
recipientsAmount: 5
107+
recipientsAmount: 9
83108
})
84109
})
85110

@@ -90,25 +115,27 @@ describe('Notifications Setup - Review presenter', () => {
90115

91116
expect(result.recipients).to.equal([
92117
{
93-
contact: ['[email protected]'],
94-
licences: [testRecipients.primaryUser.licence_refs],
95-
method: 'Letter or email - Primary user'
118+
contact: [
119+
'Mr H J Duplicate Licence holder',
120+
'4',
121+
'Privet Drive',
122+
'Little Whinging',
123+
'Surrey',
124+
'WD25 7LR'
125+
],
126+
licences: [testDuplicateRecipients.duplicateLicenceHolder.licence_refs],
127+
method: 'Letter or email - Licence holder'
96128
},
97129
{
98-
contact: ['[email protected]'],
99-
licences: [testRecipients.returnsAgent.licence_refs],
100-
method: 'Letter or email - Returns agent'
130+
contact: ['Mr H J Duplicate Returns to', '4', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
131+
licences: [testDuplicateRecipients.duplicateReturnsTo.licence_refs],
132+
method: 'Letter or email - Returns to'
101133
},
102134
{
103135
contact: ['Mr H J Licence holder', '1', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
104136
licences: [testRecipients.licenceHolder.licence_refs],
105137
method: 'Letter or email - Licence holder'
106138
},
107-
{
108-
contact: ['Mr H J Returns to', '2', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
109-
licences: [testRecipients.returnsTo.licence_refs],
110-
method: 'Letter or email - Returns to'
111-
},
112139
{
113140
contact: [
114141
'Mr H J Licence holder with multiple licences',
@@ -120,6 +147,31 @@ describe('Notifications Setup - Review presenter', () => {
120147
],
121148
licences: testRecipients.licenceHolderWithMultipleLicences.licence_refs.split(','),
122149
method: 'Letter or email - Licence holder'
150+
},
151+
{
152+
contact: ['Mr H J Returns to', '2', 'Privet Drive', 'Little Whinging', 'Surrey', 'WD25 7LR'],
153+
licences: [testRecipients.returnsTo.licence_refs],
154+
method: 'Letter or email - Returns to'
155+
},
156+
{
157+
contact: ['[email protected]'],
158+
licences: [testRecipients.primaryUser.licence_refs],
159+
method: 'Letter or email - Primary user'
160+
},
161+
{
162+
contact: ['[email protected]'],
163+
licences: [testDuplicateRecipients.duplicatePrimaryUser.licence_refs],
164+
method: 'Letter or email - Primary user'
165+
},
166+
{
167+
contact: ['[email protected]'],
168+
licences: [testRecipients.returnsAgent.licence_refs],
169+
method: 'Letter or email - Returns agent'
170+
},
171+
{
172+
contact: ['[email protected]'],
173+
licences: [testDuplicateRecipients.duplicateReturnsAgent.licence_refs],
174+
method: 'Letter or email - Returns agent'
123175
}
124176
])
125177
})
@@ -199,7 +251,7 @@ describe('Notifications Setup - Review presenter', () => {
199251

200252
describe('and there are >= 25 recipients', () => {
201253
beforeEach(() => {
202-
testInput = [...testInput, ...testInput, ...testInput, ...testInput, ...testInput, ...testInput]
254+
testInput = [...testInput, ...testInput, ...testInput]
203255

204256
pagination = {
205257
numberOfPages: 2
@@ -228,7 +280,7 @@ describe('Notifications Setup - Review presenter', () => {
228280
it('returns the remaining recipients', () => {
229281
const result = ReviewPresenter.go(testInput, page, pagination)
230282

231-
expect(result.recipients.length).to.equal(5)
283+
expect(result.recipients.length).to.equal(2)
232284
})
233285

234286
it('returns the updated "pageTitle"', () => {

test/services/notifications/setup/determine-recipients.service.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ describe('Notifications Setup - Determine Recipients service', () => {
132132
type: 'Person'
133133
},
134134
contact_hash_id: 'b1b355491c7d42778890c545e08797ea',
135-
contact_type: 'Licence holder',
135+
contact_type: 'both',
136136
email: null,
137137
licence_refs: testDuplicateRecipients.duplicateLicenceHolder.licence_refs,
138138
message_type: 'Letter'
@@ -194,7 +194,7 @@ describe('Notifications Setup - Determine Recipients service', () => {
194194
type: 'Person'
195195
},
196196
contact_hash_id: 'b1b355491c7d42778890c545e08797ea',
197-
contact_type: 'Licence holder',
197+
contact_type: 'both',
198198
email: null,
199199
licence_refs: testDuplicateRecipients.duplicateLicenceHolder.licence_refs,
200200
message_type: 'Letter'

0 commit comments

Comments
 (0)