From 12f380868f57e773e5296da95b0376bb17eb3a4d Mon Sep 17 00:00:00 2001
From: Emily Jablonski <65367387+emilyjablonski@users.noreply.github.com>
Date: Wed, 3 Jul 2024 14:32:08 -0600
Subject: [PATCH] feat: run lottery modals (#4168)
---
api/prisma/seed-helpers/address-factory.ts | 2 +-
shared-helpers/__tests__/testHelpers.ts | 2 +-
.../pages/applications/index.test.tsx | 2 +-
.../__tests__/pages/listings/lottery.test.tsx | 98 ++++++++++++++++++-
.../__tests__/pages/settings/index.test.tsx | 4 +-
.../locale_overrides/general.json | 12 ++-
.../components/shared/NavigationHeader.tsx | 2 +-
.../src/pages/listings/[id]/lottery.tsx | 96 ++++++++++++++++--
8 files changed, 198 insertions(+), 20 deletions(-)
diff --git a/api/prisma/seed-helpers/address-factory.ts b/api/prisma/seed-helpers/address-factory.ts
index 02ea1b3bdd..68bf790c17 100644
--- a/api/prisma/seed-helpers/address-factory.ts
+++ b/api/prisma/seed-helpers/address-factory.ts
@@ -37,7 +37,7 @@ export const yosemiteAddress = {
export const rockyMountainAddress = {
placeName: 'Rocky Mountain National Park',
city: 'Estes Park',
- state: 'C0',
+ state: 'CO',
street: '1000 US-36',
zipCode: '80517',
latitude: 40.3800984,
diff --git a/shared-helpers/__tests__/testHelpers.ts b/shared-helpers/__tests__/testHelpers.ts
index 20ee193672..bac17864fe 100644
--- a/shared-helpers/__tests__/testHelpers.ts
+++ b/shared-helpers/__tests__/testHelpers.ts
@@ -145,7 +145,7 @@ export const application: Application = {
updatedAt: new Date(),
placeName: "Rocky Mountain National Park",
city: "Estes Park",
- state: "C0",
+ state: "CO",
street: "1000 US-36",
zipCode: "80517",
latitude: 40.3800984,
diff --git a/sites/partners/__tests__/pages/applications/index.test.tsx b/sites/partners/__tests__/pages/applications/index.test.tsx
index 30a53cdec6..477f5699dc 100644
--- a/sites/partners/__tests__/pages/applications/index.test.tsx
+++ b/sites/partners/__tests__/pages/applications/index.test.tsx
@@ -101,7 +101,7 @@ describe("applications", () => {
expect(getByText("Mailing City")).toBeInTheDocument()
expect(getByText("Estes Park")).toBeInTheDocument()
expect(getByText("Mailing State")).toBeInTheDocument()
- expect(getByText("C0")).toBeInTheDocument()
+ expect(getByText("CO")).toBeInTheDocument()
expect(getByText("Mailing Zip")).toBeInTheDocument()
expect(getByText("80517")).toBeInTheDocument()
expect(getByText("Work Street Address")).toBeInTheDocument()
diff --git a/sites/partners/__tests__/pages/listings/lottery.test.tsx b/sites/partners/__tests__/pages/listings/lottery.test.tsx
index 9ee88ec359..b426d67ba8 100644
--- a/sites/partners/__tests__/pages/listings/lottery.test.tsx
+++ b/sites/partners/__tests__/pages/listings/lottery.test.tsx
@@ -31,6 +31,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -49,6 +52,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -69,6 +75,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -95,6 +104,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -121,6 +133,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -145,6 +160,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -175,6 +193,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -207,10 +228,13 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
- const { getByText, findByText } = render(
+ const { getByText, findByText, findAllByText } = render(
)
@@ -219,7 +243,7 @@ describe("lottery", () => {
fireEvent.click(getByText("Re-run lottery"))
expect(await findByText("Are you sure?")).toBeInTheDocument()
- expect(await findByText("I understand, re-run the lottery")).toBeInTheDocument()
+ expect(await findAllByText("Re-run lottery")).toHaveLength(2)
})
it("should show release modal if user clicks on release", async () => {
@@ -236,6 +260,9 @@ describe("lottery", () => {
}),
rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
})
)
@@ -247,11 +274,74 @@ describe("lottery", () => {
expect(header).toBeInTheDocument()
fireEvent.click(getByText("Release lottery"))
- expect(await findByText("Confirmation needed")).toBeInTheDocument()
+ expect(await findByText("Are you sure?")).toBeInTheDocument()
expect(
await findByText(
- "Releasing the lottery will give Partner users access to the lottery data, including the ability to publicize anonymous results to applicants."
+ "Releasing the lottery will give Partner users access to the lottery data, including the ability to publish results to applicants."
)
).toBeInTheDocument()
})
+
+ it("should show confirm modal if user clicks on run lottery with no unresolved duplicates", async () => {
+ mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" })
+ document.cookie = "access-token-available=True"
+ server.use(
+ rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => {
+ return res(
+ ctx.json({
+ id: "user1",
+ userRoles: { isAdmin: true },
+ })
+ )
+ }),
+ rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
+ return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 0 }))
+ })
+ )
+
+ const { getByText, findByText } = render()
+
+ const header = await findByText("Partners Portal")
+ expect(header).toBeInTheDocument()
+
+ fireEvent.click(getByText("Run lottery"))
+ expect(await findByText("Confirmation needed")).toBeInTheDocument()
+ expect(
+ await findByText("Make sure to add all paper applications before running the lottery.")
+ ).toBeInTheDocument()
+ })
+
+ it("should show confirm with duplicates modal if user clicks on run lottery and listing does have unresolved duplicates", async () => {
+ mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" })
+ document.cookie = "access-token-available=True"
+ server.use(
+ rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => {
+ return res(
+ ctx.json({
+ id: "user1",
+ userRoles: { isAdmin: true },
+ })
+ )
+ }),
+ rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => {
+ return res(ctx.json(""))
+ }),
+ rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => {
+ return res(ctx.json({ totalCount: 5, totalPendingCount: 5 }))
+ })
+ )
+
+ const { getByText, findByText, findAllByText } = render()
+
+ const header = await findByText("Partners Portal")
+ expect(header).toBeInTheDocument()
+
+ fireEvent.click(getByText("Run lottery"))
+ expect(await findByText("Confirmation needed")).toBeInTheDocument()
+ expect(await findByText("5 unresolved duplicate sets.")).toBeInTheDocument()
+ expect(await findAllByText("Run lottery")).toHaveLength(2)
+ })
})
diff --git a/sites/partners/__tests__/pages/settings/index.test.tsx b/sites/partners/__tests__/pages/settings/index.test.tsx
index 4a871bbdaa..802a89d33d 100644
--- a/sites/partners/__tests__/pages/settings/index.test.tsx
+++ b/sites/partners/__tests__/pages/settings/index.test.tsx
@@ -86,7 +86,7 @@ describe("settings", () => {
expect(headAndBody).toHaveLength(2)
const [head, body] = headAndBody
expect(within(head).getAllByRole("columnheader")).toHaveLength(4)
- const rows = within(body).getAllByRole("row")
+ const rows = await within(body).findAllByRole("row")
expect(rows).toHaveLength(1)
const [name, jurisdiction, updated, actions] = within(rows[0]).getAllByRole("cell")
expect(name).toHaveTextContent(multiselectQuestionPreference.text)
@@ -207,7 +207,7 @@ describe("settings", () => {
`This preference is currently added to listings and needs to be removed before being deleted.`
)
)
- expect(getByText(listing.name))
+ expect(await findByText(listing.name))
// verify delete button is not there
expect(queryAllByText("Delete")).toHaveLength(0)
diff --git a/sites/partners/page_content/locale_overrides/general.json b/sites/partners/page_content/locale_overrides/general.json
index c1d502ec28..6afc16c8fb 100644
--- a/sites/partners/page_content/locale_overrides/general.json
+++ b/sites/partners/page_content/locale_overrides/general.json
@@ -57,7 +57,7 @@
"applications.addConfirmModalAddApplicationPostLotteryConfirm": "I understand, add application",
"applications.addConfirmModalAddApplicationPostLotteryTitle": "Are you sure?",
"applications.addConfirmModalAddApplicationPostLotteryWeighted": "Adding a new application will require the lottery to be re-run.",
- "applications.addConfirmModalContent": "This listing has closed, are you sure you want to add an application?",
+ "applications.addConfirmModalContent": "This listing has closed. Are you sure you want to add a paper application?",
"applications.addConfirmModalHeader": "Confirmation needed",
"applications.allApplications": "All Applications",
"applications.duplicate": "Duplicate",
@@ -216,6 +216,10 @@
"listings.listingStatusText": "Listing Status",
"listings.listingSubmitted": "Listing Submitted",
"listings.longitude": "Longitude",
+ "listings.lottery.duplicateContent": "This listing has",
+ "listings.lottery.duplicateString": "%{sets} unresolved duplicate set.",
+ "listings.lottery.duplicateStringPlural": "%{sets} unresolved duplicate sets.",
+ "listings.lottery.duplicatesConfirm": "You should resolve any duplicates before running the lottery.",
"listings.lottery.export": "Export lottery data",
"listings.lottery.exportFile": "File includes randomized general pool and preference data.",
"listings.lottery.history": "History",
@@ -223,13 +227,13 @@
"listings.lottery.noDataDescription": "It looks like you haven't run a lottery for this listing yet.",
"listings.lottery.release": "Release lottery",
"listings.lottery.releaseButton": "Release lottery",
- "listings.lottery.releaseContent": "Releasing the lottery will give Partner users access to the lottery data, including the ability to publicize anonymous results to applicants.",
+ "listings.lottery.releaseContent": "Releasing the lottery will give Partner users access to the lottery data, including the ability to publish results to applicants.",
"listings.lottery.reRun": "Re-run lottery",
- "listings.lottery.reRunButton": "I understand, re-run the lottery",
- "listings.lottery.reRunContent": "Re-running the lottery will reset current preference and general pool rankings.",
+ "listings.lottery.reRunContent": "Re-running the lottery will give all applications a new raw rank, which will impact the order in which they are processed.",
"listings.lottery.reRunHistory": "Re-running a lottery will also be tracked in the history log.",
"listings.lottery.reRunCannotBeUndone": "This action cannot be undone.",
"listings.lottery.runLottery": "Run lottery",
+ "listings.lottery.runLotteryContent": "Make sure to add all paper applications before running the lottery.",
"listings.lotteryDateNotes": "Lottery Date Notes",
"listings.lotteryDateQuestion": "When will the lottery be run?",
"listings.lotteryEndTime": "Lottery End Time",
diff --git a/sites/partners/src/components/shared/NavigationHeader.tsx b/sites/partners/src/components/shared/NavigationHeader.tsx
index 38be926777..2ef0dd1e75 100644
--- a/sites/partners/src/components/shared/NavigationHeader.tsx
+++ b/sites/partners/src/components/shared/NavigationHeader.tsx
@@ -71,7 +71,7 @@ const NavigationHeader = ({
label: tabs.lotteryLabel,
path: `/listings/${listingId}/lottery`,
activePaths: [`/listings/${listingId}/lottery`],
- content: t("t.new"),
+ content: undefined,
})
}
diff --git a/sites/partners/src/pages/listings/[id]/lottery.tsx b/sites/partners/src/pages/listings/[id]/lottery.tsx
index 227aa6e7ef..c8a8b4ae63 100644
--- a/sites/partners/src/pages/listings/[id]/lottery.tsx
+++ b/sites/partners/src/pages/listings/[id]/lottery.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react"
+import React, { useState, useContext } from "react"
import Head from "next/head"
import axios from "axios"
import Ticket from "@heroicons/react/24/solid/TicketIcon"
@@ -6,10 +6,12 @@ import Download from "@heroicons/react/24/solid/ArrowDownTrayIcon"
import { t, Breadcrumbs, BreadcrumbLink } from "@bloom-housing/ui-components"
import { Button, Card, Heading, Icon, Dialog } from "@bloom-housing/ui-seeds"
import { CardHeader, CardSection } from "@bloom-housing/ui-seeds/src/blocks/Card"
+import { AuthContext } from "@bloom-housing/shared-helpers"
import {
Listing,
ListingsStatusEnum,
ReviewOrderTypeEnum,
+ ListingUpdate,
} from "@bloom-housing/shared-helpers/src/types/backend-swagger"
import Layout from "../../../layouts"
import { ListingContext } from "../../../components/listings/ListingContext"
@@ -17,6 +19,7 @@ import { MetaTags } from "../../../components/shared/MetaTags"
import ListingGuard from "../../../components/shared/ListingGuard"
import { NavigationHeader } from "../../../components/shared/NavigationHeader"
import { ListingStatusBar } from "../../../components/listings/ListingStatusBar"
+import { useFlaggedApplicationsMeta } from "../../../lib/hooks"
import styles from "../../../../styles/lottery.module.scss"
@@ -26,8 +29,14 @@ const Lottery = (props: { listing: Listing }) => {
const { listing } = props
+ const [runModal, setRunModal] = useState(false)
const [reRunModal, setReRunModal] = useState(false)
const [releaseModal, setReleaseModal] = useState(false)
+ const [loading, setLoading] = useState(false)
+
+ const { listingsService } = useContext(AuthContext)
+ const { data } = useFlaggedApplicationsMeta(listing?.id)
+ const duplicatesExist = data?.totalPendingCount > 0
if (!listing) return
{t("t.errorOccurred")}
@@ -70,7 +79,13 @@ const Lottery = (props: { listing: Listing }) => {
{t("listings.lottery.noDataDescription")}
-
+
)
@@ -174,6 +189,77 @@ const Lottery = (props: { listing: Listing }) => {
+