From 4eb50a25affbd882bdef8a2a0c70253db205302b Mon Sep 17 00:00:00 2001 From: Tausif Patel Date: Mon, 2 Sep 2024 16:29:21 +0100 Subject: [PATCH 1/6] Rename courtDateRange to dateRange to make it generic Co-Authored-By: Ian King --- src/components/CustomDateInput/DateInput.tsx | 4 ++-- .../SearchFilters/CourtDateFilter.tsx | 4 ++-- .../CourtCaseFilters/AppliedFilters.tsx | 4 ++-- .../CourtCaseFilters/CourtCaseFilter.tsx | 4 ++-- src/pages/index.tsx | 18 +++++++++++++----- src/utils/caseAgeOptions.ts | 4 ++-- src/utils/validators/validateCaseAges.ts | 4 ++-- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/components/CustomDateInput/DateInput.tsx b/src/components/CustomDateInput/DateInput.tsx index 1c7060c83..2103ea7d8 100644 --- a/src/components/CustomDateInput/DateInput.tsx +++ b/src/components/CustomDateInput/DateInput.tsx @@ -1,5 +1,5 @@ import { Dispatch } from "react" -import { SerializedCourtDateRange } from "types/CaseListQueryParams" +import { SerializedDateRange } from "types/CaseListQueryParams" import { FilterAction } from "types/CourtCaseFilter" import { SmallButton } from "./DateInput.styles" @@ -7,7 +7,7 @@ interface Props { dateType: "from" | "to" dispatch: Dispatch value: string - dateRange: SerializedCourtDateRange | undefined + dateRange: SerializedDateRange | undefined } const DateInput: React.FC = ({ dateType, dispatch, value, dateRange }: Props) => { diff --git a/src/components/SearchFilters/CourtDateFilter.tsx b/src/components/SearchFilters/CourtDateFilter.tsx index 5174aff13..84e9cd807 100644 --- a/src/components/SearchFilters/CourtDateFilter.tsx +++ b/src/components/SearchFilters/CourtDateFilter.tsx @@ -3,7 +3,7 @@ import RadioButton from "components/RadioButton/RadioButton" import ExpandingFilters from "features/CourtCaseFilters/ExpandingFilters" import { FormGroup } from "govuk-react" import type { Dispatch } from "react" -import { SerializedCourtDateRange } from "types/CaseListQueryParams" +import { SerializedDateRange } from "types/CaseListQueryParams" import type { FilterAction } from "types/CourtCaseFilter" import { CaseAgeOptions } from "utils/caseAgeOptions" import { formatDisplayedDate } from "utils/date/formattedDate" @@ -14,7 +14,7 @@ interface Props { caseAges?: string[] caseAgeCounts: Record dispatch: Dispatch - dateRange: SerializedCourtDateRange | undefined + dateRange: SerializedDateRange | undefined } const getCaseAgeWithFormattedDate = (namedCaseAge: string): string => { diff --git a/src/features/CourtCaseFilters/AppliedFilters.tsx b/src/features/CourtCaseFilters/AppliedFilters.tsx index 1a0790c34..d607be7df 100644 --- a/src/features/CourtCaseFilters/AppliedFilters.tsx +++ b/src/features/CourtCaseFilters/AppliedFilters.tsx @@ -2,7 +2,7 @@ import ConditionalRender from "components/ConditionalRender" import FilterTag from "components/FilterTag/FilterTag" import { useRouter } from "next/router" import { encode } from "querystring" -import { LockedState, Reason, SerializedCourtDateRange } from "types/CaseListQueryParams" +import { LockedState, Reason, SerializedDateRange } from "types/CaseListQueryParams" import { caseStateLabels } from "utils/caseStateFilters" import { deleteQueryParam, deleteQueryParamsByName } from "utils/deleteQueryParam" import { formatStringDateAsDisplayedDate } from "utils/date/formattedDate" @@ -15,7 +15,7 @@ interface Props { reasonCodes?: string[] ptiurn?: string | null caseAge?: string[] - dateRange?: SerializedCourtDateRange | null + dateRange?: SerializedDateRange | null lockedState?: string | null caseState?: string | null } diff --git a/src/features/CourtCaseFilters/CourtCaseFilter.tsx b/src/features/CourtCaseFilters/CourtCaseFilter.tsx index d88e85301..a2c7dd761 100644 --- a/src/features/CourtCaseFilters/CourtCaseFilter.tsx +++ b/src/features/CourtCaseFilters/CourtCaseFilter.tsx @@ -9,7 +9,7 @@ import TriggerGroups from "components/SearchFilters/TriggerGroups" import { useCurrentUser } from "context/CurrentUserContext" import { FormGroup } from "govuk-react" import { useReducer } from "react" -import { CaseListQueryParams, LockedState, SerializedCourtDateRange } from "types/CaseListQueryParams" +import { CaseListQueryParams, LockedState, SerializedDateRange } from "types/CaseListQueryParams" import type { Filter } from "types/CourtCaseFilter" import Permission from "types/Permission" import { anyFilterChips } from "utils/filterChips" @@ -26,7 +26,7 @@ const Divider = () => ( type Props = CaseListQueryParams & { caseAge: string[] caseAgeCounts: Record - dateRange: SerializedCourtDateRange | null + dateRange: SerializedDateRange | null } const CourtCaseFilter: React.FC = ({ diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 5dfdde74e..a2c9e4843 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -31,7 +31,7 @@ import { LockedState, QueryOrder, Reason, - SerializedCourtDateRange + SerializedDateRange } from "types/CaseListQueryParams" import Permission from "types/Permission" import { isError } from "types/Result" @@ -63,14 +63,15 @@ type Props = { caseAgeCounts: Record courtCases: DisplayPartialCourtCase[] csrfToken: string - dateRange: SerializedCourtDateRange | null + dateRange: SerializedDateRange | null displaySwitchingSurveyFeedback: boolean environment: string | null oppositeOrder: QueryOrder queryStringCookieName: string totalCases: number user: DisplayFullUser -} & Omit + caseResolvedDateRange: SerializedDateRange | null +} & Omit const validateOrder = (param: unknown): param is QueryOrder => param === "asc" || param === "desc" @@ -104,6 +105,7 @@ const extractSearchParamsFromQuery = (query: ParsedUrlQuery, currentUser: User): const resolvedByUsername = caseState === "Resolved" && !currentUser.hasAccessTo[Permission.ListAllCases] ? currentUser.username : null const courtDateRange = caseAges || dateRange + const resolvedDateRange = validateDateRange({ from: query.resolvedFrom, to: query.resolvedTo }) return { ...(defendantName && { defendantName: defendantName }), @@ -119,7 +121,8 @@ const extractSearchParamsFromQuery = (query: ParsedUrlQuery, currentUser: User): lockedState, ...(caseState && { caseState }), ...(resolvedByUsername && { resolvedByUsername }), - ...(allocatedToUserName && { allocatedToUserName }) + ...(allocatedToUserName && { allocatedToUserName }), + ...(resolvedDateRange && { resolvedDateRange }) } } @@ -203,7 +206,7 @@ export const getServerSideProps = withMultipleServerSideProps( logCaseListRenderTime(startTime, currentUser, caseListQueryParams) // Remove courtDateRange from the props because the dates don't serialise - const { courtDateRange: _, ...caseListQueryProps } = caseListQueryParams + const { courtDateRange: _, resolvedDateRange: __, ...caseListQueryProps } = caseListQueryParams return { props: { build: process.env.NEXT_PUBLIC_BUILD || null, @@ -224,6 +227,11 @@ export const getServerSideProps = withMultipleServerSideProps( queryStringCookieName, totalCases: courtCases.totalCases, user: userToDisplayFullUserDto(currentUser), + caseResolvedDateRange: !!caseListQueryParams.resolvedDateRange ? + { + from: formatFormInputDateString(caseListQueryParams.resolvedDateRange.from), + to: formatFormInputDateString(caseListQueryParams.resolvedDateRange.to) + } : null, ...caseListQueryProps } } diff --git a/src/utils/caseAgeOptions.ts b/src/utils/caseAgeOptions.ts index c0f8a06d7..9d53670aa 100644 --- a/src/utils/caseAgeOptions.ts +++ b/src/utils/caseAgeOptions.ts @@ -1,7 +1,7 @@ import { subDays } from "date-fns" -import { CourtDateRange } from "types/CaseListQueryParams" +import { DateRange } from "types/CaseListQueryParams" -export const CaseAgeOptions: Record CourtDateRange> = { +export const CaseAgeOptions: Record DateRange> = { Today: () => { const today = new Date() return { from: today, to: today } diff --git a/src/utils/validators/validateCaseAges.ts b/src/utils/validators/validateCaseAges.ts index 83bc38123..9754019b4 100644 --- a/src/utils/validators/validateCaseAges.ts +++ b/src/utils/validators/validateCaseAges.ts @@ -1,7 +1,7 @@ -import { CourtDateRange } from "types/CaseListQueryParams" +import { DateRange } from "types/CaseListQueryParams" import { CaseAgeOptions } from "utils/caseAgeOptions" -export const mapCaseAges = (caseAge: string | string[] | undefined): CourtDateRange[] | undefined => { +export const mapCaseAges = (caseAge: string | string[] | undefined): DateRange[] | undefined => { if (!caseAge) { return undefined } From df088c65947b43b339c97207ac2f1e3e1dc7ec04 Mon Sep 17 00:00:00 2001 From: Tausif Patel Date: Mon, 2 Sep 2024 16:30:56 +0100 Subject: [PATCH 2/6] Add optional resolvedDateRange parameter to courtCase query Co-Authored-By: Ian King --- src/services/listCourtCases.ts | 9 ++++- src/types/CaseListQueryParams.ts | 7 ++-- .../listCourtCases.integration.test.ts | 36 ++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/services/listCourtCases.ts b/src/services/listCourtCases.ts index 9f8c49af7..fc67c1fc6 100644 --- a/src/services/listCourtCases.ts +++ b/src/services/listCourtCases.ts @@ -28,7 +28,8 @@ const listCourtCases = async ( caseState, allocatedToUserName, reasonCodes, - resolvedByUsername + resolvedByUsername, + resolvedDateRange }: CaseListQueryParams, user: User ): PromiseResult => { @@ -145,6 +146,12 @@ const listCourtCases = async ( filterByReasonAndResolutionStatus(query, user, reason, reasonCodes, caseState, resolvedByUsername) + if (caseState === "Resolved" && resolvedDateRange) { + query + .andWhere({ resolutionTimestamp: MoreThanOrEqual(resolvedDateRange?.from) }) + .andWhere({ resolutionTimestamp: LessThanOrEqual(resolvedDateRange?.to) }) + } + if (allocatedToUserName) { query.andWhere( new Brackets((qb) => { diff --git a/src/types/CaseListQueryParams.ts b/src/types/CaseListQueryParams.ts index aa5d9e597..24228d429 100644 --- a/src/types/CaseListQueryParams.ts +++ b/src/types/CaseListQueryParams.ts @@ -13,12 +13,12 @@ export enum LockedState { LockedToMe = "LockedToMe" } -export type CourtDateRange = { +export type DateRange = { from: Date to: Date } -export type SerializedCourtDateRange = { +export type SerializedDateRange = { from?: string to?: string } @@ -28,7 +28,7 @@ export type CaseState = "Resolved" | "Unresolved" | undefined export type CaseListQueryParams = { allocatedToUserName?: string caseState?: CaseState - courtDateRange?: CourtDateRange | CourtDateRange[] + courtDateRange?: DateRange | DateRange[] courtName?: string defendantName?: string lockedState?: string @@ -40,4 +40,5 @@ export type CaseListQueryParams = { reason?: Reason reasonCodes?: string[] resolvedByUsername?: string + resolvedDateRange?: DateRange } diff --git a/test/services/listCourtCases/listCourtCases.integration.test.ts b/test/services/listCourtCases/listCourtCases.integration.test.ts index 7d63ceaa5..bfffd3783 100644 --- a/test/services/listCourtCases/listCourtCases.integration.test.ts +++ b/test/services/listCourtCases/listCourtCases.integration.test.ts @@ -6,7 +6,7 @@ import User from "services/entities/User" import courtCasesByOrganisationUnitQuery from "services/queries/courtCasesByOrganisationUnitQuery" import leftJoinAndSelectTriggersQuery from "services/queries/leftJoinAndSelectTriggersQuery" import { DataSource } from "typeorm" -import { LockedState } from "types/CaseListQueryParams" +import { DateRange, LockedState } from "types/CaseListQueryParams" import { ListCourtCaseResult } from "types/ListCourtCasesResult" import { ResolutionStatus } from "types/ResolutionStatus" import CourtCase from "../../../src/services/entities/CourtCase" @@ -788,4 +788,38 @@ describe("listCourtCases", () => { expect(unlockedCases.map((c) => c.errorId)).toStrictEqual([2]) }) }) + + describe("Filter cases by resolved date", () => { + it("Returns resolved cases within given date range", async () => { + const resolvedDateRange = { + from: new Date("2024-05-3"), + to: new Date("2024-05-7 23:59:59.000") + } as DateRange + await insertCourtCasesWithFields( + Array.from(Array(8)).map((_, index) => ({ + orgForPoliceFilter: orgCode, + resolutionTimestamp: new Date(`2024-05-0${index + 1}`), + errorResolvedTimestamp: new Date(`2024-05-0${index + 1}`), + triggerResolvedTimestamp: new Date(`2024-05-0${index + 1}`) + })) + ) + + const { result, totalCases } = (await listCourtCases( + dataSource, + { resolvedDateRange, caseState: "Resolved" }, + testUser + )) as ListCourtCaseResult + + console.log( + "Result: ", + result.map((r) => r.resolutionTimestamp) + ) + + expect(result).toHaveLength(5) + expect(totalCases).toBe(5) + expect(result[0].resolutionTimestamp?.toString()).toBe("Fri May 03 2024 01:00:00 GMT+0100 (British Summer Time)") + expect(result[2].resolutionTimestamp?.toString()).toBe("Sun May 05 2024 01:00:00 GMT+0100 (British Summer Time)") + expect(result[4].resolutionTimestamp?.toString()).toBe("Tue May 07 2024 01:00:00 GMT+0100 (British Summer Time)") + }) + }) }) From f74f247bbf091a46efbbd7d066e51f74c52adadf Mon Sep 17 00:00:00 2001 From: Ian King Date: Mon, 2 Sep 2024 16:34:49 +0100 Subject: [PATCH 3/6] Resolve linter issues Co-Authored-By: Tausif Patel --- src/pages/index.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a2c9e4843..b28ef496a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -227,11 +227,12 @@ export const getServerSideProps = withMultipleServerSideProps( queryStringCookieName, totalCases: courtCases.totalCases, user: userToDisplayFullUserDto(currentUser), - caseResolvedDateRange: !!caseListQueryParams.resolvedDateRange ? - { - from: formatFormInputDateString(caseListQueryParams.resolvedDateRange.from), - to: formatFormInputDateString(caseListQueryParams.resolvedDateRange.to) - } : null, + caseResolvedDateRange: caseListQueryParams.resolvedDateRange + ? { + from: formatFormInputDateString(caseListQueryParams.resolvedDateRange.from), + to: formatFormInputDateString(caseListQueryParams.resolvedDateRange.to) + } + : null, ...caseListQueryProps } } From 71a5c2c4f01d256f2e03764c7263f04be96e2799 Mon Sep 17 00:00:00 2001 From: Ian King Date: Tue, 3 Sep 2024 11:24:46 +0100 Subject: [PATCH 4/6] Remove hard coded date strings from test assertions Co-Authored-By: Bushra Abdullahi --- .../listCourtCases/listCourtCases.integration.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/services/listCourtCases/listCourtCases.integration.test.ts b/test/services/listCourtCases/listCourtCases.integration.test.ts index bfffd3783..79ddebeff 100644 --- a/test/services/listCourtCases/listCourtCases.integration.test.ts +++ b/test/services/listCourtCases/listCourtCases.integration.test.ts @@ -817,9 +817,9 @@ describe("listCourtCases", () => { expect(result).toHaveLength(5) expect(totalCases).toBe(5) - expect(result[0].resolutionTimestamp?.toString()).toBe("Fri May 03 2024 01:00:00 GMT+0100 (British Summer Time)") - expect(result[2].resolutionTimestamp?.toString()).toBe("Sun May 05 2024 01:00:00 GMT+0100 (British Summer Time)") - expect(result[4].resolutionTimestamp?.toString()).toBe("Tue May 07 2024 01:00:00 GMT+0100 (British Summer Time)") + expect(result[0].resolutionTimestamp?.toString()).toBe(new Date(`2024-05-03`).toString()) + expect(result[2].resolutionTimestamp?.toString()).toBe(new Date(`2024-05-05`).toString()) + expect(result[4].resolutionTimestamp?.toString()).toBe(new Date(`2024-05-07`).toString()) }) }) }) From 9ce34b2568d014751d97d70d392b1a5c6c6fd0f5 Mon Sep 17 00:00:00 2001 From: Ian King Date: Tue, 3 Sep 2024 11:52:34 +0100 Subject: [PATCH 5/6] resolve linter errors --- src/pages/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e973dab5c..b389bcbbf 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -107,7 +107,7 @@ const extractSearchParamsFromQuery = (query: ParsedUrlQuery, currentUser: User): const courtDateRange = caseAges || dateRange const resolvedDateRange = validateDateRange({ from: query.resolvedFrom, to: query.resolvedTo }) const asn = validateQueryParams(query.asn) ? sanitise(query.asn) : null - + return { ...(defendantName && { defendantName: defendantName }), ...(courtName && { courtName: courtName }), From e0e615af2dd2bd4d84b373296f3e21e6194894c0 Mon Sep 17 00:00:00 2001 From: Ian King Date: Tue, 3 Sep 2024 11:52:34 +0100 Subject: [PATCH 6/6] resolve linter errors --- src/services/listCourtCases.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/listCourtCases.ts b/src/services/listCourtCases.ts index dd0c2f1fe..f07b7c2a1 100644 --- a/src/services/listCourtCases.ts +++ b/src/services/listCourtCases.ts @@ -29,7 +29,7 @@ const listCourtCases = async ( allocatedToUserName, reasonCodes, resolvedByUsername, - resolvedDateRange + resolvedDateRange, asn }: CaseListQueryParams, user: User