Skip to content

Commit

Permalink
Pi 2035 activity log (#31)
Browse files Browse the repository at this point in the history
* PI-2035: Activity log
  • Loading branch information
pmcphee77 authored Mar 28, 2024
1 parent aafc7d5 commit cc19d2f
Show file tree
Hide file tree
Showing 31 changed files with 1,416 additions and 20 deletions.
3 changes: 3 additions & 0 deletions assets/scss/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ $govuk-page-width: $moj-page-width;

@import './components/header-bar';
@import './components/summary-card';
@import './components/card';
@import 'assets/scss/local';


26 changes: 26 additions & 0 deletions assets/scss/components/_card.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.app-card {
background-color: govuk-colour("light-grey");
display: block;
padding: govuk-spacing(2);
text-decoration: none;
@include govuk-font($size: 19);

@include govuk-media-query($from: desktop) {
padding: govuk-spacing(4);
}
}

.app-card--blue {
background: govuk-colour("blue");
color: govuk-colour("white");
}

.app-card--grey {
color: govuk-shade(govuk-colour("dark-grey", $legacy: "grey-1"), 30);
background: govuk-tint(govuk-colour("dark-grey", $legacy: "grey-1"), 90);
}

.note-panel {
background-color: #f3f2f1;
padding: 1.5rem
}
26 changes: 26 additions & 0 deletions assets/scss/components/_summary-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,29 @@
display: none;
}
}

.app-compliance-tag--compact {
width: 20px;
text-align: center;
float: right;
}

.govuk-tag {
display: inline-block;
outline: 2px solid transparent;
outline-offset: -2px;
letter-spacing: 1px;
max-width: none;
text-decoration: none;
text-transform: uppercase;
font-family: "GDS Transport", arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 700;
font-size: 1rem;
padding-top: 5px;
padding-right: 8px;
padding-bottom: 4px;
padding-left: 8px;
}

29 changes: 29 additions & 0 deletions integration_tests/e2e/activityLog.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Page from '../pages/page'
import ActivityLogPage from '../pages/activityLog'

context('Activity log', () => {
it('Activity log page is rendered in default view', () => {
cy.visit('/case/X000001/activity-log')
const page = Page.verifyOnPage(ActivityLogPage)
page.getRowData('timeline1', 'enforcement', 'Value').should('contain.text', 'Warning letter sent')
page.getRowData('timeline2', 'rarActivity', 'Value').should('contain.text', 'Stepping Stones')
page.getCardHeader('timeline3').should('contain.text', 'Waiting for evidence')
page.getRowData('timeline4', 'reschedule', 'Value').should('contain.text', 'Requested by Terry Jones')
page.getCardHeader('timeline5').should('contain.text', 'Office appointment at 10:15am')
page.getCardHeader('timeline6').should('contain.text', 'Phone call at 8:15am')
page.getCardHeader('timeline7').should('contain.text', 'Office appointment at 10:15am')
page.getCardHeader('timeline8').should('contain.text', 'Video call at 10:15am')
})
it('Activity log page is rendered in compact view', () => {
cy.visit('/case/X000001/activity-log?view=compact')
const page = Page.verifyOnPage(ActivityLogPage)
page.getActivity('1').should('contain.text', 'Video call')
page.getActivity('2').should('contain.text', 'Phone call from Eula Schmeler')
page.getActivity('3').should('contain.text', 'Planned appointment')
page.getActivity('4').should('contain.text', 'Initial appointment')
page.getActivity('5').should('contain.text', 'Office appointment')
page.getActivity('6').should('contain.text', 'Phone call')
page.getActivity('7').should('contain.text', 'Office appointment')
page.getActivity('8').should('contain.text', 'Video call')
})
})
9 changes: 9 additions & 0 deletions integration_tests/pages/activityLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Page, { PageElement } from './page'

export default class ActivityLogPage extends Page {
constructor() {
super('Activity log')
}

getActivity = (index: string): PageElement => cy.get(`[data-qa=timeline${index}Card]`)
}
3 changes: 2 additions & 1 deletion integration_tests/pages/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export default abstract class Page {

getTab = (tabName: string): PageElement => cy.get(`[data-qa=${tabName}Tab]`)

getCardHeader = (cardName: string): PageElement => cy.get(`[data-qa=${cardName}Card]`)
getCardHeader = (cardName: string): PageElement =>
cy.get(`[class=app-summary-card__header]`).get(`[data-qa=${cardName}Card]`)

getRowData = (cardName: string, rowName: string, type: string): PageElement => {
return cy.get(`[data-qa=${cardName}Card]`).within(() => cy.get(`[data-qa=${rowName}${type}]`))
Expand Down
5 changes: 5 additions & 0 deletions server/data/masApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from './model/personalDetails'
import { AddressOverview, PersonSummary } from './model/common'
import { SentenceDetails } from './model/sentenceDetails'
import { PersonActivity } from './model/activityLog'

export default class MasApiClient extends RestClient {
constructor(token: string) {
Expand Down Expand Up @@ -64,4 +65,8 @@ export default class MasApiClient extends RestClient {
async getPersonAppointment(crn: string, appointmentId: string): Promise<PersonAppointment | null> {
return this.get({ path: `/schedule/${crn}/appointment/${appointmentId}`, handle404: false })
}

async getPersonActivityLog(crn: string): Promise<PersonActivity> {
return this.get({ path: `/activity/${crn}`, handle404: false })
}
}
7 changes: 7 additions & 0 deletions server/data/model/activityLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PersonSummary } from './common'
import { Activity } from './schedule'

export interface PersonActivity {
personSummary: PersonSummary
activities: Activity[]
}
15 changes: 12 additions & 3 deletions server/data/model/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Document, Address } from './personalDetails'

export interface Schedule {
personSummary: PersonSummary
appointments: Appointment[]
appointments: Activity[]
}

export interface Appointment {
export interface Activity {
id?: string
type?: string
startDateTime?: string
Expand All @@ -20,6 +20,8 @@ export interface Appointment {
isInitial?: boolean
isNationalStandard?: boolean
rescheduled?: boolean
rescheduledStaff?: boolean
rescheduledPop?: boolean
didTheyComply?: boolean
absentWaitingEvidence?: boolean
rearrangeOrCancelReason?: string
Expand All @@ -31,11 +33,18 @@ export interface Appointment {
acceptableAbsence?: boolean
acceptableAbsenceReason?: string
location?: Address
action?: string
isSystemContact?: boolean
isAppointment?: boolean
isEmailOrTextFromPop?: boolean
isPhoneCallFromPop?: boolean
isEmailOrTextToPop?: boolean
isPhoneCallToPop?: boolean
lastUpdated?: string
lastUpdatedBy?: Name
}

export interface PersonAppointment {
personSummary: PersonSummary
appointment: Appointment
appointment: Activity
}
99 changes: 99 additions & 0 deletions server/routes/activityLog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { type RequestHandler, Router } from 'express'
import { auditService } from '@ministryofjustice/hmpps-audit-client'
import { v4 } from 'uuid'
import asyncMiddleware from '../middleware/asyncMiddleware'
import type { Services } from '../services'
import MasApiClient from '../data/masApiClient'

export default function activityLogRoutes(router: Router, { hmppsAuthClient }: Services) {
const get = (path: string | string[], handler: RequestHandler) => router.get(path, asyncMiddleware(handler))

get('/case/:crn/activity-log', async (req, res, _next) => {
const { crn } = req.params
const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username)
const masClient = new MasApiClient(token)

if (req.query.view === 'compact') {
res.locals.compactView = true
} else {
res.locals.defaultView = true
}

const personActivity = await masClient.getPersonActivityLog(crn)

await auditService.sendAuditMessage({
action: 'VIEW_MAS_ACTIVITY_LOG',
who: res.locals.user.username,
subjectId: crn,
subjectType: 'CRN',
correlationId: v4(),
service: 'hmpps-manage-a-supervision-ui',
})
res.render('pages/activity-log', {
personActivity,
crn,
})
})

get('/case/:crn/activity-log/:category', async (req, res, _next) => {
const { crn, category } = req.params
const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username)

await auditService.sendAuditMessage({
action: 'VIEW_MAS_ACTIVITY_LOG_CATEGORY',
who: res.locals.user.username,
subjectId: crn,
subjectType: 'CRN',
correlationId: v4(),
service: 'hmpps-manage-a-supervision-ui',
})

const masClient = new MasApiClient(token)

const personActivity = await masClient.getPersonActivityLog(crn)

if (req.query.view === 'compact') {
res.locals.compactView = true
} else {
res.locals.defaultView = true
}

res.render('pages/activity-log', {
category,
personActivity,
crn,
})
})

get('/case/:crn/activity-log/activity/:id', async (req, res, _next) => {
const { crn, id } = req.params
const token = await hmppsAuthClient.getSystemClientToken(res.locals.user.username)

const masClient = new MasApiClient(token)
const personAppointment = await masClient.getPersonAppointment(crn, id)
const isActivityLog = true
const queryParams: string[] = []

const { category } = req.query
if (req.query.view) {
queryParams.push(`view=${req.query.view}`)
}

await auditService.sendAuditMessage({
action: 'VIEW_MAS_ACTIVITY_LOG_DETAIL',
who: res.locals.user.username,
subjectId: crn,
subjectType: 'CRN',
correlationId: v4(),
service: 'hmpps-manage-a-supervision-ui',
})

res.render('pages/schedule/appointment', {
category,
queryParams,
personAppointment,
crn,
isActivityLog,
})
})
}
2 changes: 2 additions & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import caseRoutes from './case'
import personalDetailRoutes from './personalDetails'
import sentenceRoutes from './sentence'
import scheduleRoutes from './schedule'
import activityLogRoutes from './activityLog'

export default function routes(services: Services): Router {
const router = Router()
Expand All @@ -17,5 +18,6 @@ export default function routes(services: Services): Router {
personalDetailRoutes(router, services)
sentenceRoutes(router, services)
scheduleRoutes(router, services)
activityLogRoutes(router, services)
return router
}
1 change: 0 additions & 1 deletion server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export const services = () => {
environment: config.env,
extraColumns: [],
})

return {
applicationInfo,
hmppsAuthClient,
Expand Down
6 changes: 6 additions & 0 deletions server/utils/nunjucksSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import path from 'path'
import nunjucks from 'nunjucks'
import express from 'express'
import {
activityLog,
activityLogDate,
addressToList,
compactActivityLogDate,
dateWithDayAndWithoutYear,
dateWithNoDay,
dateWithYear,
Expand Down Expand Up @@ -82,6 +85,9 @@ export default function nunjucksSetup(app: express.Express, applicationInfo: App
njkEnv.addFilter('deliusDateFormat', deliusDateFormat)
njkEnv.addFilter('dayOfWeek', dayOfWeek)
njkEnv.addFilter('toYesNo', toYesNo)
njkEnv.addFilter('compactActivityLogDate', compactActivityLogDate)
njkEnv.addFilter('activityLogDate', activityLogDate)
njkEnv.addGlobal('activityLog', activityLog)
njkEnv.addGlobal('getRisksToThemselves', getRisksToThemselves)
njkEnv.addGlobal('getCurrentRisksToThemselves', getCurrentRisksToThemselves)
njkEnv.addGlobal('getPreviousRisksToThemselves', getPreviousRisksToThemselves)
Expand Down
Loading

0 comments on commit cc19d2f

Please sign in to comment.