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

Add resolved date range to case list query #718

Merged
4 changes: 2 additions & 2 deletions src/components/CustomDateInput/DateInput.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Dispatch } from "react"
import { SerializedCourtDateRange } from "types/CaseListQueryParams"
import { SerializedDateRange } from "types/CaseListQueryParams"
import { FilterAction } from "types/CourtCaseFilter"
import { SmallButton } from "./DateInput.styles"

interface Props {
dateType: "from" | "to"
dispatch: Dispatch<FilterAction>
value: string
dateRange: SerializedCourtDateRange | undefined
dateRange: SerializedDateRange | undefined
}

const DateInput: React.FC<Props> = ({ dateType, dispatch, value, dateRange }: Props) => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/SearchFilters/CourtDateFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -14,7 +14,7 @@ interface Props {
caseAges?: string[]
caseAgeCounts: Record<string, number>
dispatch: Dispatch<FilterAction>
dateRange: SerializedCourtDateRange | undefined
dateRange: SerializedDateRange | undefined
}

const getCaseAgeWithFormattedDate = (namedCaseAge: string): string => {
Expand Down
4 changes: 2 additions & 2 deletions src/features/CourtCaseFilters/AppliedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions src/features/CourtCaseFilters/CourtCaseFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -26,7 +26,7 @@ const Divider = () => (
type Props = CaseListQueryParams & {
caseAge: string[]
caseAgeCounts: Record<string, number>
dateRange: SerializedCourtDateRange | null
dateRange: SerializedDateRange | null
}

const CourtCaseFilter: React.FC<Props> = ({
Expand Down
20 changes: 15 additions & 5 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
LockedState,
QueryOrder,
Reason,
SerializedCourtDateRange
SerializedDateRange
} from "types/CaseListQueryParams"
import Permission from "types/Permission"
import { isError } from "types/Result"
Expand Down Expand Up @@ -63,14 +63,15 @@ type Props = {
caseAgeCounts: Record<string, number>
courtCases: DisplayPartialCourtCase[]
csrfToken: string
dateRange: SerializedCourtDateRange | null
dateRange: SerializedDateRange | null
displaySwitchingSurveyFeedback: boolean
environment: string | null
oppositeOrder: QueryOrder
queryStringCookieName: string
totalCases: number
user: DisplayFullUser
} & Omit<CaseListQueryParams, "allocatedToUserName" | "resolvedByUsername" | "courtDateRange">
caseResolvedDateRange: SerializedDateRange | null
} & Omit<CaseListQueryParams, "allocatedToUserName" | "resolvedByUsername" | "courtDateRange" | "resolvedDateRange">

const validateOrder = (param: unknown): param is QueryOrder => param === "asc" || param === "desc"

Expand Down Expand Up @@ -104,7 +105,9 @@ 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 })
const asn = validateQueryParams(query.asn) ? sanitise(query.asn) : null

return {
...(defendantName && { defendantName: defendantName }),
...(courtName && { courtName: courtName }),
Expand All @@ -120,7 +123,8 @@ const extractSearchParamsFromQuery = (query: ParsedUrlQuery, currentUser: User):
lockedState,
...(caseState && { caseState }),
...(resolvedByUsername && { resolvedByUsername }),
...(allocatedToUserName && { allocatedToUserName })
...(allocatedToUserName && { allocatedToUserName }),
...(resolvedDateRange && { resolvedDateRange })
}
}

Expand Down Expand Up @@ -204,7 +208,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,
Expand All @@ -225,6 +229,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,
...caseListQueryProps
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/services/listCourtCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const listCourtCases = async (
allocatedToUserName,
reasonCodes,
resolvedByUsername,
resolvedDateRange,
asn
}: CaseListQueryParams,
user: User
Expand Down Expand Up @@ -150,6 +151,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) => {
Expand Down
7 changes: 4 additions & 3 deletions src/types/CaseListQueryParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -40,5 +40,6 @@ export type CaseListQueryParams = {
reason?: Reason
reasonCodes?: string[]
resolvedByUsername?: string
resolvedDateRange?: DateRange
asn?: string
}
4 changes: 2 additions & 2 deletions src/utils/caseAgeOptions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { subDays } from "date-fns"
import { CourtDateRange } from "types/CaseListQueryParams"
import { DateRange } from "types/CaseListQueryParams"

export const CaseAgeOptions: Record<string, () => CourtDateRange> = {
export const CaseAgeOptions: Record<string, () => DateRange> = {
Today: () => {
const today = new Date()
return { from: today, to: today }
Expand Down
4 changes: 2 additions & 2 deletions src/utils/validators/validateCaseAges.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
36 changes: 35 additions & 1 deletion test/services/listCourtCases/listCourtCases.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -816,4 +816,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(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())
})
})
})