Skip to content

Commit

Permalink
Show domainEvents in the Timeline
Browse files Browse the repository at this point in the history
Currently, we will only be showing domainEvents when "accommodation is required from"  or the "Release date" has been updated.
  • Loading branch information
aliuk2012 committed Nov 19, 2024
1 parent 402e694 commit 52371c6
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 5 deletions.
2 changes: 2 additions & 0 deletions server/testutils/factories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import premisesSummaryFactory from './premisesSummary'
import prisonCaseNotesFactory from './prisonCaseNotes'
import probationRegionFactory from './probationRegion'
import referenceDataFactory from './referenceData'
import referralHistoryDomainEventNoteFactory from './referralHistoryDomainEventNote'
import referralHistorySystemNoteFactory from './referralHistorySystemNote'
import referralHistoryUserNoteFactory from './referralHistoryUserNote'
import risksFactory from './risks'
Expand Down Expand Up @@ -126,6 +127,7 @@ export {
prisonCaseNotesFactory,
probationRegionFactory,
referenceDataFactory,
referralHistoryDomainEventNoteFactory,
referralHistorySystemNoteFactory,
referralHistoryUserNoteFactory,
restrictedPersonFactory,
Expand Down
26 changes: 26 additions & 0 deletions server/testutils/factories/referralHistoryDomainEventNote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ReferralHistoryDomainEventNote, ReferralHistoryNoteMessageDetails } from '@approved-premises/api'
import { fakerEN_GB as faker } from '@faker-js/faker'
import { Factory } from 'fishery'
import { DateFormats } from '../../utils/dateUtils'

export default Factory.define<ReferralHistoryDomainEventNote>(() => ({
id: faker.string.uuid(),
createdByUserName: faker.person.fullName(),
createdAt: DateFormats.dateObjToIsoDate(faker.date.past()),
message: '',
messageDetails: {
domainEvent: {
id: faker.string.uuid(),
eventType: 'accommodation.cas3.assessment.updated',
timestamp: DateFormats.dateObjToIsoDate(faker.date.past()),
updatedFields: [
{
fieldName: 'releaseDate',
updatedTo: '2025-09-02',
updatedFrom: '2123-09-02',
},
],
},
} as ReferralHistoryNoteMessageDetails,
type: 'domainEvent',
}))
138 changes: 136 additions & 2 deletions server/utils/assessmentUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { AssessmentSearchApiStatus } from '@approved-premises/ui'
import type { ReferralHistoryNoteMessageDetails } from '@approved-premises/api'
import {
ReferralHistoryDomainEventNote as DomainEventNote,
ReferralHistoryNoteMessageDetails,
} from '@approved-premises/api'
import { fakerEN_GB as faker } from '@faker-js/faker'
import paths from '../paths/temporary-accommodation/manage'
import {
applicationFactory,
Expand All @@ -8,6 +12,7 @@ import {
personFactory,
placeContextFactory,
referenceDataFactory,
referralHistoryDomainEventNoteFactory,
referralHistorySystemNoteFactory,
referralHistoryUserNoteFactory,
restrictedPersonFactory,
Expand All @@ -22,6 +27,7 @@ import {
insertUpdateDateError,
pathFromStatus,
referralRejectionReasonIsOther,
renderDomainEventNote,
renderNote,
renderSystemNote,
statusChangeMessage,
Expand All @@ -30,6 +36,7 @@ import {
import * as assessmentUtils from './assessmentUtils'
import * as viewUtils from './viewUtils'
import { addPlaceContext, addPlaceContextFromAssessmentId, createPlaceContext } from './placeUtils'
import { DateFormats } from './dateUtils'

jest.mock('./userUtils')
jest.mock('./placeUtils')
Expand Down Expand Up @@ -295,6 +302,25 @@ describe('assessmentUtils', () => {
expect(assessmentUtils.renderSystemNote).toHaveBeenCalledWith(note)
})

it('renders the contents of a domain event note with details', () => {
jest.spyOn(assessmentUtils, 'renderDomainEventNote').mockReturnValue('formatted message')
const note: DomainEventNote = {
id: faker.string.uuid(),
createdByUserName: faker.person.fullName(),
createdAt: DateFormats.dateObjToIsoDate(faker.date.past()),
type: 'domainEvent',
message: '',
messageDetails: {
domainEvent: { foo: 'bar' },
} as ReferralHistoryNoteMessageDetails,
}

const result = renderNote(note)

expect(result).toEqual('formatted message')
expect(assessmentUtils.renderDomainEventNote).toHaveBeenCalledWith(note.messageDetails)
})

it('returns undefined for a system note with no message details', () => {
const note = referralHistorySystemNoteFactory.build({
message: '',
Expand Down Expand Up @@ -344,6 +370,52 @@ describe('assessmentUtils', () => {
})
})

describe('renderDomainEventDetails', () => {
describe('when "Accommodation required from date" has been updated', () => {
it('returns HTML for a standard rejection reason', () => {
const messageDetails: DomainEventNote['messageDetails'] = {
domainEvent: {
eventType: 'accommodation.cas3.assessment.updated',
updatedFields: [
{
fieldName: 'accommodationRequiredFromDate',
updatedTo: '2125-11-01',
updatedFrom: '2125-01-31',
},
],
},
}

const result = renderDomainEventNote(messageDetails)

expect(result).toEqual(
'<p>Accommodation required from date was changed from 31 January 2125 to 1 November 2125</p>',
)
})
})

describe('when "Release date" has been updated', () => {
it('returns HTML for a standard rejection reason', () => {
const messageDetails: DomainEventNote['messageDetails'] = {
domainEvent: {
eventType: 'accommodation.cas3.assessment.updated',
updatedFields: [
{
fieldName: 'releaseDate',
updatedTo: '2125-11-01',
updatedFrom: '2125-01-31',
},
],
},
}

const result = renderDomainEventNote(messageDetails)

expect(result).toEqual('<p>Release date was changed from 31 January 2125 to 1 November 2125</p>')
})
})
})

describe('timelineItems', () => {
it('returns a notes in a format compatible with the MoJ timeline component', () => {
const userNote1 = referralHistoryUserNoteFactory.build({
Expand All @@ -365,14 +437,76 @@ describe('assessmentUtils', () => {
category: 'ready_to_place',
})

const notes = [systemNote1, systemNote2, userNote2, userNote1]
const domainEventNote1 = referralHistoryDomainEventNoteFactory.build({
createdByUserName: 'SOME USER',
createdAt: '2024-06-02',
messageDetails: {
domainEvent: {
eventType: 'accommodation.cas3.assessment.updated',
timestamp: DateFormats.dateObjToIsoDate(faker.date.past()),
updatedFields: [
{
fieldName: 'accommodationRequiredFromDate',
updatedTo: '2025-09-02',
updatedFrom: '2123-09-02',
},
],
},
},
})

const domainEventNote2 = referralHistoryDomainEventNoteFactory.build({
createdByUserName: 'SOME USER',
createdAt: '2024-06-01',
messageDetails: {
domainEvent: {
eventType: 'accommodation.cas3.assessment.updated',
timestamp: DateFormats.dateObjToIsoDate(faker.date.past()),
updatedFields: [
{
fieldName: 'releaseDate',
updatedTo: '2025-09-02',
updatedFrom: '2123-09-02',
},
],
},
},
})

const notes = [systemNote1, systemNote2, userNote2, userNote1, domainEventNote1, domainEventNote2]
const assessment = assessmentFactory.build({ referralHistoryNotes: notes })
const userNoteHtml = 'some formatted html'

jest.spyOn(viewUtils, 'formatLines').mockReturnValue(userNoteHtml)
const result = timelineItems(assessment)

expect(result).toEqual([
{
label: {
text: 'Accommodation required from date updated',
},
html: '<p>Accommodation required from date was changed from 2 September 2123 to 2 September 2025</p>',
datetime: {
timestamp: domainEventNote1.createdAt,
type: 'datetime',
},
byline: {
text: 'Some User',
},
},
{
label: {
text: 'Release date updated',
},
html: '<p>Release date was changed from 2 September 2123 to 2 September 2025</p>',
datetime: {
timestamp: domainEventNote2.createdAt,
type: 'datetime',
},
byline: {
text: 'Some User',
},
},
{
label: {
text: 'Referral marked as ready to place',
Expand Down
41 changes: 38 additions & 3 deletions server/utils/assessmentUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AssessmentSortField,
TemporaryAccommodationAssessmentStatus as AssessmentStatus,
TemporaryAccommodationAssessmentSummary as AssessmentSummary,
ReferralHistoryDomainEventNote as DomainEventNote,
SortDirection,
ReferralHistorySystemNote as SystemNote,
ReferralHistoryUserNote as UserNote,
Expand Down Expand Up @@ -135,12 +136,14 @@ export const assessmentActions = (assessment: Assessment) => {
return items
}

const timeLineLabelText = (note: UserNote | SystemNote): string => {
const timeLineLabelText = (note: UserNote | SystemNote | DomainEventNote): string => {
switch (note.type) {
case 'domainEvent':
return domainEventLabelText(note.messageDetails as DomainEventNote['messageDetails'])
case 'user':
return 'Note'
case 'system':
return systemNoteLabelText(note)
return systemNoteLabelText(note as SystemNote)
default:
throw new Error(`Unknown type of timeline item - ${note.type}`)
}
Expand Down Expand Up @@ -210,6 +213,10 @@ export const statusChangeMessage = (assessmentId: string, status: AssessmentStat
}
}

const isDomainEventNote = (note: UserNote | SystemNote | DomainEventNote) => {
return Boolean(note.type === 'domainEvent')
}

const isUserNote = (note: UserNote | SystemNote): note is UserNote => {
return Boolean(note.type === 'user')
}
Expand All @@ -222,6 +229,18 @@ const isSystemNoteWithDetails = (note: UserNote | SystemNote): note is SystemNot
return Boolean(isSystemNote(note) && note.messageDetails)
}

export const renderDomainEventNote = (note: DomainEventNote['messageDetails']): TimelineItem['html'] | never => {
const updatedField = note.domainEvent.updatedFields[0]
switch (updatedField.fieldName) {
case 'accommodationRequiredFromDate':
return `<p>Accommodation required from date was changed from ${DateFormats.isoDateToUIDate(updatedField.updatedFrom)} to ${DateFormats.isoDateToUIDate(updatedField.updatedTo)}</p>`
case 'releaseDate':
return `<p>Release date was changed from ${DateFormats.isoDateToUIDate(updatedField.updatedFrom)} to ${DateFormats.isoDateToUIDate(updatedField.updatedTo)}</p>`
default:
return assertUnreachable(updatedField.fieldName as never)
}
}

export const renderSystemNote = (note: SystemNote): TimelineItem['html'] => {
const reason = note.messageDetails.rejectionReasonDetails || note.messageDetails.rejectionReason
const isWithdrawn = note.messageDetails.isWithdrawn ? 'Yes' : 'No'
Expand All @@ -231,7 +250,7 @@ export const renderSystemNote = (note: SystemNote): TimelineItem['html'] => {
return formatLines(lines.join('\n\n'))
}

export const renderNote = (note: UserNote | SystemNote): TimelineItem['html'] => {
export const renderNote = (note: UserNote | SystemNote | DomainEventNote): TimelineItem['html'] => {
if (isSystemNoteWithDetails(note)) {
return renderSystemNote(note)
}
Expand All @@ -240,6 +259,10 @@ export const renderNote = (note: UserNote | SystemNote): TimelineItem['html'] =>
return formatLines(note.message)
}

if (isDomainEventNote(note)) {
return renderDomainEventNote((note as DomainEventNote).messageDetails)
}

return undefined
}

Expand All @@ -261,6 +284,18 @@ const systemNoteLabelText = (note: SystemNote): TimelineItem['label']['text'] =>
return assertUnreachable(note.category)
}
}

const domainEventLabelText = (note: DomainEventNote['messageDetails']): TimelineItem['label']['text'] => {
switch (note.domainEvent.updatedFields[0].fieldName) {
case 'accommodationRequiredFromDate':
return 'Accommodation required from date updated'
case 'releaseDate':
return 'Release date updated'
default:
return assertUnreachable(note.domainEvent.updatedFields[0].fieldName as never)
}
}

export const createTableHeadings = (
currentSortBy: AssessmentSortField,
sortIsAscending: boolean,
Expand Down

0 comments on commit 52371c6

Please sign in to comment.