Skip to content

Commit

Permalink
BICAW-2610 note preview (ministryofjustice#192)
Browse files Browse the repository at this point in the history
* Refactored NoteTag component

* created show/hide button functionality

* show / hide classes working

* bare bones

* added JSS custom styling framework

* imported SVG from figma

* extracted most recent note

* WIP

* added note created date into preview container

* Fixed column width in CourtCaseList.tsx

* WIP

* WIP

* WIP

* WIP- start of Alice's plan

* WIP-

* WIP

* Note preview functional

* Added basic gds stylings

* Removed top border on notesRow

* set column widths to prevent 2 line headers

* removed margin on button

* refactored redundant comments

* Update note message logic

* changed name of tableBody

* refactored tablebody into separate component

* Added TODOs

* re-ordered helper functions in courCaseListEntry component

* added elipsis logic on displayed note text

* WIP tests to do

* Refactor logic into testable functions

* WIP setting up test

* Added test for getMostRecentNote function

* WIP- truncating function not working

* fixed truncating test

* added logic to filter out system notes

* refactored function calls

* removed console.logs

* moved file locations and updated component testing for icons

* refactored component test

* added e2e test skeleton

* updated display behaviour when there are 0 user notes

* tidy up removed console log

* updated e2e test to include note preview

* removed note preview stories

* turned on reactStrictMode

* removed unused icon components

* removed brackets

* removed duplicate tests

* fixed breaking storybook test

* updated path for conditional render

* added more assertions to component tests

* updated review notes

* fixed tests

* updated helper function name

* updated path for gds colours

---------

Co-authored-by: joe-fol <[email protected]>
  • Loading branch information
donnyhyon and joe-fol authored Mar 1, 2023
1 parent b76ff7b commit 0c5bda7
Show file tree
Hide file tree
Showing 12 changed files with 533 additions and 423 deletions.
14 changes: 14 additions & 0 deletions cypress/component/NotePreview.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { HideButton, PreviewButton } from "../../src/features/CourtCaseList/NotePreviewButton"

describe("NotePreview.cy.tx", () => {
it("shows the default down chevron with the label 'preview'", () => {
cy.mount(<PreviewButton />)
cy.get(".govuk-accordion-nav__chevron--down").should("exist")
cy.get(".govuk-accordion-nav__chevron").should("exist")
})

it("shows the default up chevron with the label 'hide'", () => {
cy.mount(<HideButton />)
cy.get(".govuk-accordion-nav__chevron").should("exist")
})
})
115 changes: 3 additions & 112 deletions cypress/e2e/index.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,37 +208,6 @@ describe("Case list", () => {
})
})

it("should be able to navigate to the case details page and back", () => {
cy.task("insertMultipleDummyCourtCases", { numToInsert: 3, force: "01" })

loginAndGoToUrl()

cy.findByText("Defendant Name 0").click()

cy.url().should("match", /\/court-cases\//)
cy.findByText("Case Details").should("exist")

cy.findByText("Cases").click()

cy.url().should("match", /\/bichard/)
cy.findByText("Court cases").should("exist")
})

it("Should display the urgent badge on cases marked as urgent", () => {
cy.task("insertCourtCasesWithFields", [
{ isUrgent: true, orgForPoliceFilter: "01" },
{ isUrgent: false, orgForPoliceFilter: "01" },
{ isUrgent: true, orgForPoliceFilter: "01" }
])

loginAndGoToUrl()

cy.get("tr").not(":first").eq(0).get("td:nth-child(5)").contains(`Case00000`)
cy.get("tr").not(":first").eq(0).contains(`Urgent`).should("exist")
cy.get("tr").not(":first").eq(1).contains(`Urgent`).should("not.exist")
cy.get("tr").not(":first").eq(2).contains(`Urgent`).should("exist")
})

it("Should display the resolved badge on cases marked as resolved", () => {
cy.task("insertCourtCasesWithFields", [
{ resolutionTimestamp: new Date(), orgForPoliceFilter: "01" },
Expand Down Expand Up @@ -305,63 +274,6 @@ describe("Case list", () => {
cy.get("tr").not(":first").eq(2).get("td:nth-child(7)").contains(`3`).should("exist")
})

it("Should display reason (errors and triggers) with correct formatting", () => {
cy.task("insertCourtCasesWithFields", [{ orgForPoliceFilter: "011111" }, { orgForPoliceFilter: "011111" }])

cy.task("insertException", {
caseId: 0,
exceptionCode: "HO100310",
errorReport: "HO100310||ds:OffenceReasonSequence"
})
})

it("should display cases for parent forces up to the second-level force", () => {
cy.task("insertCourtCasesWithFields", [
{ orgForPoliceFilter: "01" },
{ orgForPoliceFilter: "011" },
{ orgForPoliceFilter: "0111" },
{ orgForPoliceFilter: "01111" },
{ orgForPoliceFilter: "011111" }
])

loginAndGoToUrl("[email protected]")

cy.get("tr").not(":first").get("td:nth-child(5)").contains("Case00000").should("not.exist")
cy.get("tr").not(":first").get("td:nth-child(5)").contains("Case00001").should("not.exist")
cy.get("tr").not(":first").get("td:nth-child(5)").contains("Case00002")
cy.get("tr").not(":first").get("td:nth-child(5)").contains("Case00003")
cy.get("tr").not(":first").get("td:nth-child(5)").contains("Case00004")
})

it("can display cases ordered by court name", () => {
cy.task("insertCourtCasesWithFields", [
{ courtName: "BBBB", orgForPoliceFilter: "011111" },
{ courtName: "AAAA", orgForPoliceFilter: "011111" },
{ courtName: "DDDD", orgForPoliceFilter: "011111" },
{ courtName: "CCCC", orgForPoliceFilter: "011111" }
])

loginAndGoToUrl()

cy.findByText("Court Name").click()

cy.get("tr")
.not(":first")
.each((row) => {
cy.wrap(row).get("td:nth-child(4)").first().contains("AAAA")
cy.wrap(row).get("td:nth-child(4)").last().contains("DDDD")
})

cy.findByText("Court Name").click()

cy.get("tr")
.not(":first")
.each((row) => {
cy.wrap(row).get("td:nth-child(4)").first().contains("DDDD")
cy.wrap(row).get("td:nth-child(4)").last().contains("AAAA")
})
})

it("should be able to navigate to the case details page and back", () => {
cy.task("insertMultipleDummyCourtCases", { numToInsert: 3, force: "01" })

Expand Down Expand Up @@ -393,26 +305,7 @@ describe("Case list", () => {
cy.get("tr").not(":first").eq(2).contains(`Urgent`).should("exist")
})

it("Should display the resolved badge on cases marked as resolved", () => {
cy.task("insertCourtCasesWithFields", [
{ resolutionTimestamp: new Date(), orgForPoliceFilter: "01" },
{ resolutionTimestamp: null, orgForPoliceFilter: "01" },
{ resolutionTimestamp: new Date(), orgForPoliceFilter: "01" }
])

loginAndGoToUrl()

cy.get("#filter-button").contains("Show filter").click()
cy.get("#unresolved-and-resolved").click()
cy.get("#search").contains("Apply filters").click()

cy.get("tr").not(":first").eq(0).get("td:nth-child(5)").contains(`Case00000`)
cy.get("tr").not(":first").eq(0).contains(`Resolved`).should("exist")
cy.get("tr").not(":first").eq(1).contains(`Resolved`).should("not.exist")
cy.get("tr").not(":first").eq(2).contains(`Resolved`).should("exist")
})

it("Should display the correct number of user-created notes on cases", () => {
it("Should display a preview of the notes", () => {
const caseNotes: { user: string; text: string }[][] = [
[
{
Expand Down Expand Up @@ -453,10 +346,8 @@ describe("Case list", () => {

loginAndGoToUrl()

cy.get("tr").not(":first").eq(0).get("td:nth-child(5)").contains(`Case00000`)
cy.get("tr").not(":first").eq(0).get("td:nth-child(7)").should("be.empty")
cy.get("tr").not(":first").eq(1).get("td:nth-child(7)").contains(`1`).should("exist")
cy.get("tr").not(":first").eq(2).get("td:nth-child(7)").contains(`3`).should("exist")
cy.get("tr").not(":first").eq(1).get("td:nth-child(7)").contains(`Preview`).should("exist").trigger("click")
cy.contains(`Test note 1`).should("exist")
})

it("Should display reason (errors and triggers) with correct formatting", () => {
Expand Down
14 changes: 7 additions & 7 deletions src/features/CourtCaseList/CourtCaseList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ OneRecord.play = async ({ canvasElement }) => {
expect(urgentTags).toHaveLength(2) // The column header also matches this

expect(canvas.getByText("Notes")).toBeInTheDocument()
expect(canvas.getByText("2")).toBeInTheDocument()
expect(canvas.getByText("2 notes")).toBeInTheDocument()
}
OneRecord.parameters = {
design: [
Expand Down Expand Up @@ -92,7 +92,7 @@ ManyRecords.play = async ({ canvasElement }) => {
expect(urgentTags).toHaveLength(courtCases.length + 1) // The column header also matches this

expect(canvas.getByText("Notes")).toBeInTheDocument()
const noteTags = await canvas.findAllByText("2")
const noteTags = await canvas.findAllByText("2 notes")
expect(noteTags).toHaveLength(courtCases.length)
}
ManyRecords.parameters = {
Expand Down Expand Up @@ -157,7 +157,7 @@ MixedNotes.play = async ({ canvasElement }) => {
const canvas = within(canvasElement)

expect(canvas.getByText("Notes")).toBeInTheDocument()
const noteTags = await canvas.findAllByText("2")
const noteTags = await canvas.findAllByText("2 notes")
expect(noteTags).toHaveLength(mixedNotes.filter((c) => c.notes.length).length)
}
MixedNotes.parameters = {
Expand Down Expand Up @@ -213,9 +213,9 @@ CaseLockedToUser.play = async ({ canvasElement }) => {
const canvas = within(canvasElement)

const expectedUnlockButtons = await canvas.findAllByRole("button")
expect(expectedUnlockButtons).toHaveLength(2)
expect(expectedUnlockButtons[0]).toHaveTextContent("Bugs Bunny")
expect(expectedUnlockButtons).toHaveLength(3)
expect(expectedUnlockButtons[1]).toHaveTextContent("Bugs Bunny")
expect(expectedUnlockButtons[2]).toHaveTextContent("Bugs Bunny")
}

const superUser = {
Expand All @@ -231,7 +231,7 @@ CaseViewedBySupervisor.play = async ({ canvasElement }) => {
const canvas = within(canvasElement)

const expectedUnlockButtons = await canvas.findAllByRole("button")
expect(expectedUnlockButtons).toHaveLength(2)
expect(expectedUnlockButtons[0]).toHaveTextContent("Bugs Bunny")
expect(expectedUnlockButtons).toHaveLength(3)
expect(expectedUnlockButtons[1]).toHaveTextContent("Bugs Bunny")
expect(expectedUnlockButtons[2]).toHaveTextContent("Bugs Bunny")
}
153 changes: 11 additions & 142 deletions src/features/CourtCaseList/CourtCaseList.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,10 @@
import DateTime from "components/DateTime"
import ConditionalRender from "components/ConditionalRender"
import ColumnOrderIcons from "features/CourtCaseFilters/ColumnOrderIcons"
import { GridRow, Link, Paragraph, Table } from "govuk-react"
import Image from "next/image"
import { Link, Paragraph, Table } from "govuk-react"
import { useRouter } from "next/router"
import { encode } from "querystring"
import { createUseStyles } from "react-jss"
import CourtCase from "services/entities/CourtCase"
import User from "services/entities/User"
import type { QueryOrder } from "types/CaseListQueryParams"
import { deleteQueryParamsByName } from "utils/deleteQueryParam"
import getTriggerWithDescription from "utils/formatReasons/getTriggerWithDescription"
import groupErrorsFromReport from "utils/formatReasons/groupErrorsFromReport"
import { displayedDateFormat } from "utils/formattedDate"
import LockedByTag from "./tags/LockedByTag"
import NotesTag from "./tags/NotesTag"
import ResolvedTag from "./tags/ResolvedTag"
import UrgentTag from "./tags/UrgentTag"

const useStyles = createUseStyles({
caseDetailsRow: {
verticalAlign: "top"
},
triggersRow: {
verticalAlign: "top",
backgroundColor: "#f3f2f1" // GDS light-grey color
}
})
import CourtCaseListEntry from "./CourtCaseListEntry"

interface Props {
courtCases: CourtCase[]
Expand All @@ -35,34 +13,19 @@ interface Props {
}

const CourtCaseList: React.FC<Props> = ({ courtCases, order = "asc", currentUser }: Props) => {
const classes = useStyles()
const { basePath, query } = useRouter()

let searchParams = new URLSearchParams(encode(query))
searchParams = deleteQueryParamsByName(["unlockException", "unlockTrigger"], searchParams)

const orderByParams = (orderBy: string) => `${basePath}/?${new URLSearchParams({ ...query, orderBy, order })}`
const caseDetailsPath = (id: number) => `${basePath}/court-cases/${id}`
const unlockCaseWithReasonPath = (reason: "Trigger" | "Exception", caseId: string) => {
searchParams.append(`unlock${reason}`, caseId)
return `${basePath}/?${searchParams}`
}

const canUnlockCase = (lockedUsername: string): boolean => {
return currentUser.groups.includes("Supervisor") || currentUser.username === lockedUsername
}

const tableHead = (
<Table.Row>
<Table.Cell></Table.Cell>
<Table.CellHeader>
<Table.CellHeader setWidth={"178px"}>
<ColumnOrderIcons columnName={"defendantName"} currentOrder={query.order} orderBy={query.orderBy}>
<Link href={orderByParams("defendantName")} id="defendant-name-sort">
{"Defendant Name"}
</Link>
</ColumnOrderIcons>
</Table.CellHeader>
<Table.CellHeader>
<Table.CellHeader setWidth={"115px"}>
<ColumnOrderIcons columnName={"courtDate"} currentOrder={query.order} orderBy={query.orderBy}>
<Link href={orderByParams("courtDate")} id="court-date-sort">
{"Court Date"}
Expand Down Expand Up @@ -107,112 +70,18 @@ const CourtCaseList: React.FC<Props> = ({ courtCases, order = "asc", currentUser
</Table.CellHeader>
</Table.Row>
)
const tableBody: JSX.Element[] = []
courtCases.forEach(
(
{
errorId,
courtDate,
ptiurn,
defendantName,
courtName,
triggers,
errorReport,
isUrgent,
notes,
errorLockedByUsername,
triggerLockedByUsername,
resolutionTimestamp
},
idx
) => {
const exceptions = groupErrorsFromReport(errorReport)
tableBody.push(
<Table.Row key={`case-details-row-${idx}`} className={classes.caseDetailsRow}>
<Table.Cell>
<ConditionalRender isRendered={!!errorLockedByUsername}>
<Image src={"/bichard/assets/images/lock.svg"} width={20} height={20} alt="Lock icon" />
</ConditionalRender>
</Table.Cell>
<Table.Cell>
<Link href={caseDetailsPath(courtCases[idx].errorId)} id={`Case details for ${defendantName}`}>
{defendantName}
<br />
<ResolvedTag isResolved={resolutionTimestamp !== null} />
</Link>
</Table.Cell>
<Table.Cell>
<DateTime date={courtDate} dateFormat={displayedDateFormat} />
</Table.Cell>
<Table.Cell>{courtName}</Table.Cell>
<Table.Cell>{ptiurn}</Table.Cell>
<Table.Cell>
<UrgentTag isUrgent={isUrgent} />
</Table.Cell>
<Table.Cell>
<NotesTag notes={notes} />
</Table.Cell>
<Table.Cell>
{Object.keys(exceptions).map((code, codeId) => (
<GridRow key={`exception_${codeId}`}>
{code}
<b>&nbsp;{exceptions[code] > 1 ? `(${exceptions[code]})` : ""}</b>
</GridRow>
))}
</Table.Cell>
<Table.Cell>
{errorLockedByUsername && canUnlockCase(errorLockedByUsername) ? (
<LockedByTag
lockedBy={errorLockedByUsername}
unlockPath={unlockCaseWithReasonPath("Exception", `${errorId}`)}
/>
) : (
<LockedByTag lockedBy={errorLockedByUsername} />
)}
</Table.Cell>
</Table.Row>
)

if (triggers.length > 0) {
tableBody.push(
<Table.Row key={`triggers-row-${idx}`} className={classes.triggersRow}>
<Table.Cell>
<ConditionalRender isRendered={!!triggerLockedByUsername}>
<Image src={"/bichard/assets/images/lock.svg"} width={20} height={20} alt="Lock icon" />
</ConditionalRender>
</Table.Cell>
<Table.Cell />
<Table.Cell />
<Table.Cell />
<Table.Cell />
<Table.Cell />
<Table.Cell />
<Table.Cell>
{triggers?.map((trigger, triggerId) => (
<GridRow key={`trigger_${triggerId}`}>{getTriggerWithDescription(trigger.triggerCode)}</GridRow>
))}
</Table.Cell>
<Table.Cell>
{triggerLockedByUsername && canUnlockCase(triggerLockedByUsername) ? (
<LockedByTag
lockedBy={triggerLockedByUsername}
unlockPath={unlockCaseWithReasonPath("Trigger", `${errorId}`)}
/>
) : (
<LockedByTag lockedBy={triggerLockedByUsername} />
)}
</Table.Cell>
</Table.Row>
)
}
}
)

if (courtCases.length === 0) {
return <Paragraph>{"There are no court cases to show"}</Paragraph>
}

return <Table head={tableHead}>{tableBody}</Table>
return (
<Table head={tableHead}>
{courtCases.map((courtCase, idx) => (
<CourtCaseListEntry courtCase={courtCase} currentUser={currentUser} key={`court-case-${idx}`} />
))}
</Table>
)
}

export default CourtCaseList
Loading

0 comments on commit 0c5bda7

Please sign in to comment.