Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Man 82 activity log #289

Merged
merged 28 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
47b8e94
MAN-82 activity log filter menu ui wip
neil-mills Jan 15, 2025
4f7feb1
MAN-82 working draft of filter menu functionality
neil-mills Jan 16, 2025
4a8a2f3
MAN-82 filter menu date range validation wip
neil-mills Jan 17, 2025
f3c0b71
MAN-167 add invalid date format regex validation
neil-mills Jan 20, 2025
ab47ca1
MAN-167 redirect url if validation passes
neil-mills Jan 20, 2025
8fc4d13
MAN-82 improve type definitions of res.locals vars
neil-mills Jan 21, 2025
7b2401b
MAN-82 activity log filter menu wip
neil-mills Jan 21, 2025
17c4987
MAN-82 various bug fixes, working pagination
neil-mills Jan 22, 2025
d44b1fe
MAN-82 add no results state
neil-mills Jan 22, 2025
fa6f58e
MAN-82 add caching for activity log request
neil-mills Jan 23, 2025
1dbb173
MAN-82 update ui for activity log result cards
neil-mills Jan 23, 2025
1e25b47
MAN-226 Add record and outcome filter
neil-mills Jan 23, 2025
28df42b
MAN-82 fix bug in results caching
neil-mills Jan 23, 2025
297b37b
MAN-226 rename request body property from 'compliance' to 'filters'
neil-mills Jan 23, 2025
2230587
MAN-167 refactor date range validation
neil-mills Jan 24, 2025
da4cd78
MAN-82 add filter menu integration tests wip
neil-mills Jan 24, 2025
48b1666
MAN-82 filter menu integration tests + bug fixes
neil-mills Jan 27, 2025
86b2139
MAN-82 pr fixes
neil-mills Jan 28, 2025
5f96322
MAN-82 fix failing int tests
neil-mills Jan 28, 2025
3de36d8
MAN-82 Sonar fixes
neil-mills Jan 28, 2025
b012ac3
Merge remote-tracking branch 'origin/main' into MAN-82-activity-log-r…
neil-mills Jan 28, 2025
acd7315
MAN-82 further sonar exception fixes
neil-mills Jan 28, 2025
e7870ec
MAN-82 fix regex sonar exception
neil-mills Jan 28, 2025
19b7e23
MAN-82 update styling of results count
neil-mills Jan 28, 2025
3186887
MAN-82 fix sonar code exception issue
neil-mills Jan 29, 2025
b9d0841
MAN-82 fix further sonar code exception issues
neil-mills Jan 29, 2025
4a0884c
MAN-82 fix further sonar code exception issue
neil-mills Jan 29, 2025
5c98547
MAN-82 fix further sonar code exception issue
neil-mills Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions assets/scss/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,23 @@ li.ui-timepicker-selected .ui-timepicker-duration,
.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {
background: #f2f2f2;
}

.toggle-menu {
list-style-type: none;
margin: 0;
padding: 0;
}
.toggle-menu__list-item {
display: inline-block;
&:not(:first-child):before {
content: '|';
padding: 0 2px;
}
}
.govuk-tag--orange {
color: #ffffff;
background-color: #f47738;
}
.govuk-colour--red {
color: govuk-colour('red');
}
34 changes: 17 additions & 17 deletions assets/scss/components/_summary-card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/* Card header */
.app-summary-card__header {
align-items: center;
background-color: govuk-colour("light-grey");
background-color: govuk-colour('light-grey');
padding: govuk-spacing(3);

@include govuk-media-query($from: desktop) {
Expand Down Expand Up @@ -88,58 +88,58 @@
float: right;
}


.app-compliance-panel {
padding: govuk-spacing(3);
color: govuk-colour("white");
background: govuk-colour("dark-grey");

p, ul, li, h2, h3, a {
color: govuk-colour('white');
background: govuk-colour('dark-grey');

p,
ul,
li,
h2,
h3,
a {
color: inherit;
}
}

.app-compliance-panel--red {
background: govuk-colour("red");
background: govuk-colour('red');
}

.app-compliance-panel--green {
background: govuk-colour("green");
background: govuk-colour('green');
}

.app-compliance-panel--blue {
background: govuk-colour("blue");
background: govuk-colour('blue');
}

.app-compliance-panel--orange {
background: govuk-colour("orange");
background: govuk-colour('orange');
}

.app-summary-card--compliance {
border-bottom-width: 0;
}

.app-tag--dark-red {
background: govuk-colour("red");
background: govuk-colour('red');
}


.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;
font-family: 'GDS Transport', arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 700;
font-weight: 400;
font-size: 1rem;
padding-top: 5px;
padding-right: 8px;
padding-bottom: 4px;
padding-left: 8px;
}

417 changes: 414 additions & 3 deletions integration_tests/e2e/activityLog.cy.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions integration_tests/e2e/licence-condition-note.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ context('Sentence', () => {
const page = Page.verifyOnPage(SentencePage)
page.headerCrn().should('contain.text', 'X000001')
page.headerName().should('contain.text', 'Caroline Wolff')

cy.get('[data-qa=pageHeading]').eq(0).should('contain.text', 'Sentence')

cy.get(`[class=predictor-timeline-item__level]`)
Expand Down
31 changes: 31 additions & 0 deletions integration_tests/pages/activityLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,36 @@ export default class ActivityLogPage extends Page {
super('Activity log')
}

getApplyFiltersButton = (): PageElement => cy.get('[data-qa="submit-button"]')

getKeywordsInput = (): PageElement => cy.get('[data-qa="keywords"] input')

getDateFromInput = (): PageElement => cy.get('[data-qa="date-from"] input')

getDateFromToggle = (): PageElement => cy.get('[data-qa="date-from"] .moj-datepicker__toggle')

getDateFromDialog = (): PageElement => cy.get('[data-qa="date-from"] .moj-datepicker__dialog')

getDateToInput = (): PageElement => cy.get('[data-qa="date-to"] input')

getDateToToggle = (): PageElement => cy.get('[data-qa="date-to"] .moj-datepicker__toggle')

getDateToDialog = (): PageElement => cy.get('[data-qa="date-to"] .moj-datepicker__dialog')

getSelectedFilterTags = (): PageElement => cy.get('.moj-filter__tag')

getSelectedFilterTag = (index: number) => cy.get(`.moj-filter-tags li:nth-of-type(${index}) a`)

getActivity = (index: string): PageElement => cy.get(`[data-qa=timeline${index}Card]`)

getComplianceFilter = (index: number): PageElement =>
cy.get(`[data-qa="compliance"] .govuk-checkboxes__item:nth-of-type(${index}) input`)

getPaginationLink = (index: number): PageElement => cy.get(`.govuk-pagination li:nth-of-type(${index}) a`)

getPaginationItem = (index: number): PageElement => cy.get(`.govuk-pagination li:nth-of-type(${index})`)

getTimelineCard = (index: number): PageElement => cy.get(`.app-summary-card:nth-type-of(${index})`)

getNoResults = (): PageElement => cy.get('[data-qa="no-results"]')
}
38 changes: 38 additions & 0 deletions server/@types/ActivityLog.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PersonActivity } from '../data/model/activityLog'
import { TierCalculation } from '../data/tierApiClient'
import type { Errors, Option } from './index'

export interface ActivityLogFilters {
keywords: string
dateFrom: string
dateTo: string
compliance: string[]
}

export interface ActivityLogRequestBody {
keywords: string
dateFrom: string
dateTo: string
filters: string[]
}

export interface SelectedFilterItem {
text: string
href: string
}

export interface ActivityLogFiltersResponse extends ActivityLogFilters {
errors: Errors
selectedFilterItems: SelectedFilterItem[]
complianceOptions: Option[]
baseUrl: string
queryStr: string
queryStrPrefix: string
queryStrSuffix: string
maxDate: string
}

export interface ActivityLogCache extends ActivityLogFilters {
personActivity: PersonActivity
tierCalculation: TierCalculation
}
11 changes: 8 additions & 3 deletions server/@types/Appointment.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export interface Appointment {
date: string
'start-time': string
'end-time': string
repeating: 'Yes' | 'No'
'repeating-frequency': string
'repeating-count': string
repeating?: 'Yes' | 'No'
'repeating-frequency'?: string
'repeating-count'?: string
id?: string
}

Expand All @@ -16,6 +16,11 @@ export type AppointmentType =
| 'PlannedOfficeVisitNS'
| 'InitialAppointmentHomeVisitNS'

export interface AppointmentTypeOption {
text: string
value: AppointmentType
}

export type AppointmentInterval = 'DAY' | 'WEEK' | 'FORTNIGHT' | 'FOUR_WEEKS'

export interface AppointmentRequestBody {
Expand Down
11 changes: 2 additions & 9 deletions server/@types/Data.type.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
/* eslint-disable import/no-cycle */
import { Location } from '../data/model/caseload'
import { PersonalDetails } from '../data/model/personalDetails'
import { Sentence } from '../data/model/sentenceDetails'
import { Errors } from './Errors.type'

interface Appointment {
type?: string
location?: string
date?: string
'start-time'?: string
'end-time'?: string
}
import { Errors, Appointment } from './index'

export interface Data {
appointments?: {
Expand Down
5 changes: 5 additions & 0 deletions server/@types/Option.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Option {
text: string
value?: string
checked?: boolean
}
38 changes: 37 additions & 1 deletion server/@types/Route.type.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,39 @@
/* eslint-disable import/no-cycle */
import { Request, Response, NextFunction } from 'express'
import { ActivityLogFiltersResponse, Appointment, AppointmentTypeOption, Errors, Option } from './index'
import { PersonalDetails } from '../data/model/personalDetails'
import { FeatureFlags } from '../data/model/featureFlags'
import { Sentence } from '../data/model/sentenceDetails'
import { Location } from '../data/model/caseload'
import { SentryConfig } from '../config'

export type Route<T> = (req: Request, res: Response, next?: NextFunction) => T
interface Locals {
filters?: ActivityLogFiltersResponse
user: { token: string; authSource: string; username?: string }
compactView?: boolean
defaultView?: boolean
requirement?: string
appointment?: Appointment
case?: PersonalDetails
message?: string
status?: number
stack?: boolean | number | string
flags?: FeatureFlags
sentences?: Sentence[]
timeOptions?: Option[]
userLocations?: Location[]
sentry?: SentryConfig
csrfToken?: string
cspNonce?: string
errors?: Errors
change?: string
appointmentTypes?: AppointmentTypeOption[]
lastAppointmentDate?: string
version: string
}

export interface AppResponse extends Response {
locals: Locals
}

export type Route<T> = (req: Request, res: AppResponse, next?: NextFunction) => T
6 changes: 5 additions & 1 deletion server/@types/express/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UserDetails } from '../../services/userService'
import { Errors } from '../Errors.type'
import { UserLocations } from '../../data/model/caseload'
import { Data } from '../index'
import { ActivityLogCache, Data } from '../index'

export default {}

Expand All @@ -16,6 +16,10 @@ declare module 'express-session' {
sortBy: string
caseFilter: CaseFilter
data?: Data
errors?: Errors
cache?: {
activityLog: ActivityLogCache[]
}
}

interface CaseFilter {
Expand Down
3 changes: 3 additions & 0 deletions server/@types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/* eslint-disable import/no-cycle */
export * from './Route.type'
export * from './Errors.type'
export * from './Data.type'
export * from './Appointment.type'
export * from './ActivityLog.type'
export * from './Option.type'
8 changes: 8 additions & 0 deletions server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export interface ApiConfig {
agent: AgentConfig
}

export interface SentryConfig {
dsn: string
loaderScriptId: string
tracesSampleRate: number
replaySampleRate: number
replayOnErrorSampleRate: number
}

export default {
buildNumber: get('BUILD_NUMBER', '1_0_0', requiredInProduction),
productId: get('PRODUCT_ID', 'UNASSIGNED', requiredInProduction),
Expand Down
16 changes: 15 additions & 1 deletion server/data/masApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { TeamCaseload, UserCaseload, UserTeam, UserLocations } from './model/cas
import { ProfessionalContact } from './model/professionalContact'
import { CaseAccess, UserAccess } from './model/caseAccess'
import { LicenceConditionNoteDetails } from './model/licenceConditionNoteDetails'
import { AppointmentRequestBody } from '../@types'
import { AppointmentRequestBody, ActivityLogRequestBody } from '../@types'
import { RequirementNoteDetails } from './model/requirementNoteDetails'

export default class MasApiClient extends RestClient {
Expand Down Expand Up @@ -124,6 +124,20 @@ export default class MasApiClient extends RestClient {
return this.get({ path: `/activity/${crn}`, handle404: false })
}

postPersonActivityLog = async (
crn: string,
body: ActivityLogRequestBody,
page: string,
): Promise<PersonActivity | null> => {
const pageQuery = `?${new URLSearchParams({ size: '10', page }).toString()}`
return this.post({
data: body,
path: `/activity/${crn}${pageQuery}`,
handle404: true,
handle500: true,
})
}

async getPersonRiskFlags(crn: string): Promise<PersonRiskFlags> {
return this.get({ path: `/risk-flags/${crn}`, handle404: false })
}
Expand Down
4 changes: 4 additions & 0 deletions server/data/model/activityLog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { PersonSummary } from './common'
import { Activity } from './schedule'

export interface PersonActivity {
size: number
page: number
totalResults: number
totalPages: number
personSummary: PersonSummary
activities: Activity[]
}
5 changes: 3 additions & 2 deletions server/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Request, Response, NextFunction } from 'express'
import type { Request, NextFunction } from 'express'
import type { HTTPError } from 'superagent'
import logger from '../logger'
import type { AppResponse } from './@types'

export default function createErrorHandler(production: boolean) {
return (error: HTTPError, req: Request, res: Response, next: NextFunction): void => {
return (error: HTTPError, req: Request, res: AppResponse, next: NextFunction): void => {
logger.error(`Error handling request for '${req.originalUrl}', user '${res.locals.user?.username}'`, error)

if (error.status === 401 || error.status === 403) {
Expand Down
5 changes: 3 additions & 2 deletions server/middleware/asyncMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Request, Response, NextFunction, RequestHandler } from 'express'
import type { Request, NextFunction, RequestHandler } from 'express'
import { AppResponse } from '../@types'

export default function asyncMiddleware(fn: RequestHandler) {
return (req: Request, res: Response, next: NextFunction): void => {
return (req: Request, res: AppResponse, next: NextFunction): void => {
Promise.resolve(fn(req, res, next)).catch(next)
}
}
Loading
Loading