Skip to content

Commit

Permalink
Add resolvedByUsername to case list query (#719)
Browse files Browse the repository at this point in the history
* update logic and write test

* refactor logic and update test

* refactor function to helper file

* WIP

* WIP

* add test for formatName

* WIP

* complete test
  • Loading branch information
BushraAbdullahi authored Sep 6, 2024
1 parent 8391826 commit 162538d
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 20 deletions.
9 changes: 9 additions & 0 deletions src/helpers/formatName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function formatName(name: string) {
let splitName = name.replace(/\*|\s+/g, "%")

if (!name.includes("%") && !splitName.endsWith("%")) {
splitName = `${splitName}%`
}

return splitName
}
83 changes: 63 additions & 20 deletions src/services/listCourtCases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { CaseListQueryParams, LockedState } from "types/CaseListQueryParams"
import { ListCourtCaseResult } from "types/ListCourtCasesResult"
import Permission from "types/Permission"
import PromiseResult from "types/PromiseResult"

import { isError } from "types/Result"
import CourtCase from "./entities/CourtCase"
import getLongTriggerCode from "./entities/transformers/getLongTriggerCode"
import User from "./entities/User"
import filterByReasonAndResolutionStatus from "./filters/filterByReasonAndResolutionStatus"
import courtCasesByOrganisationUnitQuery from "./queries/courtCasesByOrganisationUnitQuery"
import leftJoinAndSelectTriggersQuery from "./queries/leftJoinAndSelectTriggersQuery"
import { formatName } from "../helpers/formatName"

const listCourtCases = async (
connection: DataSource,
Expand Down Expand Up @@ -60,7 +60,9 @@ const listCourtCases = async (
"courtCase.errorLockedByUsername",
"courtCase.triggerLockedByUsername"
])

query = courtCasesByOrganisationUnitQuery(query, user)

leftJoinAndSelectTriggersQuery(query, user.excludedTriggers, caseState ?? "Unresolved")
.leftJoinAndSelect("courtCase.notes", "note")
.leftJoin("courtCase.errorLockedByUser", "errorLockedByUser")
Expand All @@ -86,27 +88,31 @@ const listCourtCases = async (

// Filters
if (defendantName) {
let splitDefendantName = defendantName.replace(/\*|\s+/g, "%")

if (!splitDefendantName.endsWith("%")) {
splitDefendantName = `${splitDefendantName}%`
}
const splitDefendantName = formatName(defendantName)

query.andWhere({ defendantName: ILike(splitDefendantName) })
query.andWhere({
defendantName: ILike(splitDefendantName)
})
}

if (courtName) {
const courtNameLike = { courtName: ILike(`%${courtName}%`) }
const courtNameLike = {
courtName: ILike(`%${courtName}%`)
}
query.andWhere(courtNameLike)
}

if (ptiurn) {
const ptiurnLike = { ptiurn: ILike(`%${ptiurn}%`) }
const ptiurnLike = {
ptiurn: ILike(`%${ptiurn}%`)
}
query.andWhere(ptiurnLike)
}

if (asn) {
query.andWhere({ asn: ILike(`%${asn}%`) })
query.andWhere({
asn: ILike(`%${asn}%`)
})
}

if (reasonCodes?.length) {
Expand All @@ -119,7 +125,7 @@ const listCourtCases = async (
.orWhere("courtCase.error_report ilike any(array[:...firstExceptions])", {
firstExceptions: reasonCodes.map((reasonCode) => `${reasonCode}||%`)
})
// match exceptions ins the middle of the error report
// match exceptions in the middle of the error report
.orWhere("courtCase.error_report ilike any(array[:...exceptions])", {
exceptions: reasonCodes.map((reasonCode) => `% ${reasonCode}||%`)
})
Expand All @@ -135,20 +141,43 @@ const listCourtCases = async (
qb.orWhere(
new Brackets((dateRangeQuery) => {
dateRangeQuery
.andWhere({ courtDate: MoreThanOrEqual(dateRange.from) })
.andWhere({ courtDate: LessThanOrEqual(dateRange.to) })
.andWhere({
courtDate: MoreThanOrEqual(dateRange.from)
})
.andWhere({
courtDate: LessThanOrEqual(dateRange.to)
})
})
)
})
})
)
} else {
query
.andWhere({ courtDate: MoreThanOrEqual(courtDateRange.from) })
.andWhere({ courtDate: LessThanOrEqual(courtDateRange.to) })
.andWhere({
courtDate: MoreThanOrEqual(courtDateRange.from)
})
.andWhere({
courtDate: LessThanOrEqual(courtDateRange.to)
})
}
}

if (resolvedByUsername) {
const splitResolvedByUsername = formatName(resolvedByUsername)

query.andWhere(
new Brackets((qb) => {
qb.where({
errorResolvedBy: ILike(splitResolvedByUsername)
}).orWhere({
triggerResolvedBy: ILike(splitResolvedByUsername)
})
})
)
}

// Existing filters
filterByReasonAndResolutionStatus(query, user, reason, reasonCodes, caseState, resolvedByUsername)

if (caseState === "Resolved" && resolvedDateRange) {
Expand All @@ -160,7 +189,9 @@ const listCourtCases = async (
if (allocatedToUserName) {
query.andWhere(
new Brackets((qb) => {
qb.where({ errorLockedByUsername: allocatedToUserName }).orWhere({
qb.where({
errorLockedByUsername: allocatedToUserName
}).orWhere({
triggerLockedByUsername: allocatedToUserName
})
})
Expand All @@ -171,13 +202,21 @@ const listCourtCases = async (
if (lockedState === LockedState.Locked) {
query.andWhere(
new Brackets((qb) => {
qb.where({ errorLockedByUsername: Not(IsNull()) }).orWhere({ triggerLockedByUsername: Not(IsNull()) })
qb.where({
errorLockedByUsername: Not(IsNull())
}).orWhere({
triggerLockedByUsername: Not(IsNull())
})
})
)
} else if (lockedState === LockedState.Unlocked) {
query.andWhere(
new Brackets((qb) => {
qb.where({ errorLockedByUsername: IsNull() }).andWhere({ triggerLockedByUsername: IsNull() })
qb.where({
errorLockedByUsername: IsNull()
}).andWhere({
triggerLockedByUsername: IsNull()
})
})
)
}
Expand All @@ -188,11 +227,15 @@ const listCourtCases = async (
}

if (!user.hasAccessTo[Permission.Triggers]) {
query.andWhere({ errorCount: MoreThan(0) })
query.andWhere({
errorCount: MoreThan(0)
})
}

if (!user.hasAccessTo[Permission.Exceptions]) {
query.andWhere({ triggerCount: MoreThan(0) })
query.andWhere({
triggerCount: MoreThan(0)
})
}

const result = await query.getManyAndCount().catch((error: Error) => error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import CourtCase from "services/entities/CourtCase"
import User from "services/entities/User"
import getDataSource from "services/getDataSource"
import listCourtCases from "services/listCourtCases"
import courtCasesByOrganisationUnitQuery from "services/queries/courtCasesByOrganisationUnitQuery"
import leftJoinAndSelectTriggersQuery from "services/queries/leftJoinAndSelectTriggersQuery"
import { DataSource } from "typeorm"
import { ListCourtCaseResult } from "types/ListCourtCasesResult"
import { isError } from "../../../src/types/Result"
import { hasAccessToAll } from "../../helpers/hasAccessTo"
import deleteFromEntity from "../../utils/deleteFromEntity"
import { insertCourtCasesWithFields } from "../../utils/insertCourtCases"
import Trigger from "../../../src/services/entities/Trigger"
import Note from "services/entities/Note"

jest.mock("services/queries/courtCasesByOrganisationUnitQuery")
jest.mock("services/queries/leftJoinAndSelectTriggersQuery")

jest.setTimeout(100000)
describe("listCourtCases", () => {
let dataSource: DataSource
const forceCode = "036"
const testUser = {
visibleForces: [forceCode],
visibleCourts: [],
hasAccessTo: hasAccessToAll
} as Partial<User> as User

beforeAll(async () => {
dataSource = await getDataSource()
})

beforeEach(async () => {
await deleteFromEntity(CourtCase)
await deleteFromEntity(Trigger)
await deleteFromEntity(Note)
jest.resetAllMocks()
jest.clearAllMocks()
;(courtCasesByOrganisationUnitQuery as jest.Mock).mockImplementation(
jest.requireActual("services/queries/courtCasesByOrganisationUnitQuery").default
)
;(leftJoinAndSelectTriggersQuery as jest.Mock).mockImplementation(
jest.requireActual("services/queries/leftJoinAndSelectTriggersQuery").default
)
})

afterAll(async () => {
if (dataSource) {
await dataSource.destroy()
}
})

describe("search by resolvedByUsername", () => {
it("Should list cases that match the partial username search", async () => {
await insertCourtCasesWithFields([
{ errorResolvedBy: "User Name01" },
{ triggerResolvedBy: "User Name02" },
{ errorResolvedBy: "User Name03" }
])

const result = await listCourtCases(dataSource, { maxPageItems: 100, resolvedByUsername: "User" }, testUser)
expect(isError(result)).toBe(false)
const { result: cases } = result as ListCourtCaseResult

expect(cases).toHaveLength(3)
expect(cases[0].errorResolvedBy).toStrictEqual("User Name01")
expect(cases[1].triggerResolvedBy).toStrictEqual("User Name02")
expect(cases[2].errorResolvedBy).toStrictEqual("User Name03")
})

it("Should list cases that match the full username search", async () => {
await insertCourtCasesWithFields([
{ errorResolvedBy: "User Name01" },
{ triggerResolvedBy: "User Name02" },
{ errorResolvedBy: "User Name03" }
])

const result = await listCourtCases(
dataSource,
{ maxPageItems: 100, resolvedByUsername: "User Name01" },
testUser
)
expect(isError(result)).toBe(false)
const { result: cases } = result as ListCourtCaseResult

expect(cases).toHaveLength(1)
expect(cases[0].errorResolvedBy).toStrictEqual("User Name01")
})

it("Should handle wildcard searches for partial usernames", async () => {
await insertCourtCasesWithFields([
{ errorResolvedBy: "User Name01" },
{ triggerResolvedBy: "User Name02" },
{ errorResolvedBy: "User Name03" }
])

const result = await listCourtCases(dataSource, { maxPageItems: 100, resolvedByUsername: "%Name0%" }, testUser)
expect(isError(result)).toBe(false)
const { result: cases } = result as ListCourtCaseResult

expect(cases).toHaveLength(3)
expect(cases[0].errorResolvedBy).toStrictEqual("User Name01")
expect(cases[1].triggerResolvedBy).toStrictEqual("User Name02")
expect(cases[2].errorResolvedBy).toStrictEqual("User Name03")
})
})
})
33 changes: 33 additions & 0 deletions test/services/listCourtCases/formatName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { formatName } from "../../../src/helpers/formatName"

describe("formatName", () => {
it('should replace spaces with "%" and add "%" at the end', () => {
expect(formatName("John Doe")).toBe("John%Doe%")
})

it('should replace "*" with "%" and add "%" at the end', () => {
expect(formatName("John*Doe")).toBe("John%Doe%")
})

it('should not add another "%" if already present at the end', () => {
expect(formatName("John Doe%")).toBe("John%Doe%")
})

it('should add "%" if there are no spaces or special characters', () => {
expect(formatName("John")).toBe("John%")
})

it("should handle empty string", () => {
expect(formatName("")).toBe("%")
})

it("should handle a wildcard character anywhere in the string", () => {
expect(formatName("Name0%")).toBe("Name0%")
expect(formatName("User%Name")).toBe("User%Name")
expect(formatName("%Name")).toBe("%Name")
})

it('should still replace spaces and append "%" at the end if no wildcard is present', () => {
expect(formatName("Name 0")).toBe("Name%0%")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ describe("listCourtCases", () => {
expect(cases[1].ptiurn).toStrictEqual(ptiurnToIncludeWithPartialMatch)
})
})

describe("search by reason", () => {
it("Should list cases when there is a case insensitive match in triggers or exceptions", async () => {
await insertCourtCasesWithFields(Array.from({ length: 4 }, () => ({ orgForPoliceFilter: orgCode })))
Expand Down

0 comments on commit 162538d

Please sign in to comment.