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

feat: frontend expiring certification warnings and sign-in prevention #205

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

mathcolo
Copy link
Collaborator

@mathcolo mathcolo commented Dec 20, 2024

Asana Task: 📐 Implement Warnings for Expiring Certifications

image image image image

Checklist

  • https://github.com/mbta/terraform_modules/pull/58
  • Tests:
    • (x) Has tests
    • ( ) Doesn't need tests
    • ( ) Tests deferred (with justification)
  • Product/Design sign off:
    • (x) Okayed the plan for the feature (e.g. the design files, or the Asana task)
    • ( ) Reviewed the feature as implemented (e.g. on dev-green, or saw screenshots)
    • ( ) No review needed

@mathcolo mathcolo force-pushed the pim-load-certifications-frontend branch from f2d8c5e to 614450e Compare January 2, 2025 20:38
@mathcolo mathcolo added the deploy-to-sandbox deploy this PR to sandbox label Jan 2, 2025
@mathcolo mathcolo marked this pull request as ready for review January 2, 2025 23:23
@mathcolo mathcolo requested a review from a team as a code owner January 2, 2025 23:23
Copy link
Member

@skyqrose skyqrose left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was some talk about whether we should warn on someone not having any certifications in the database. I don't remember the resolution of that discussion. Should anything like that be incorporated into this feature?

return list.map(certificationFromData);
};

export const useCertifications = (badge: string | null) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Can you add a return type to this? Sometimes its obvious, but in this case it isn't (you have to go through the type of useApiResult and parse to figure it out), and this is a pretty important abstraction boundary so should be clearly typed.


export type Certification = {
type: string;
rail_line: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Should this be railLine? The json data below definitely shouldn't be, but this one is the type we'd use within the react app.

</form>
</>
: <>
<Instructions displayName={name} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming: Instructions is a bit ambiguous, when I first saw this I thought it meant instructions for filling out the form normally, but this is specifically instructions for dealing with an expired cert.

Comment on lines +232 to +267
test("displays expired mode if expired", () => {
const view = render(
<Attestation
badge="123"
prefill={false}
onComplete={jest.fn()}
loading={false}
employees={EMPLOYEES}
certifications={CERTIFICATIONS_ONE_EXPIRED}
/>,
);
expect(view.getByText("Expired card")).toBeInTheDocument();
expect(
view.getByText("Continue to Fit for Duty Check."),
).toBeInTheDocument();
});

test("can bypass expired mode", async () => {
const view = render(
<Attestation
badge="123"
prefill={false}
onComplete={jest.fn()}
loading={false}
employees={EMPLOYEES}
certifications={CERTIFICATIONS_ONE_EXPIRED}
/>,
);
await userEvent.click(
view.getByRole("button", { name: "Continue to Fit for Duty Check →" }),
);
expect(
view.getByRole("button", { name: "Complete Fit for Duty Check" }),
).toBeInTheDocument();
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests are redundant. You don't need to assert the existence of the button and then assert that you can click it, just one test that asserts clicking the button would work.

I suggest deleting the first test, and copying the one unique assertion from that test (getByText("ExpiredCard")) into the second.

Suggested change
test("displays expired mode if expired", () => {
const view = render(
<Attestation
badge="123"
prefill={false}
onComplete={jest.fn()}
loading={false}
employees={EMPLOYEES}
certifications={CERTIFICATIONS_ONE_EXPIRED}
/>,
);
expect(view.getByText("Expired card")).toBeInTheDocument();
expect(
view.getByText("Continue to Fit for Duty Check."),
).toBeInTheDocument();
});
test("can bypass expired mode", async () => {
const view = render(
<Attestation
badge="123"
prefill={false}
onComplete={jest.fn()}
loading={false}
employees={EMPLOYEES}
certifications={CERTIFICATIONS_ONE_EXPIRED}
/>,
);
await userEvent.click(
view.getByRole("button", { name: "Continue to Fit for Duty Check →" }),
);
expect(
view.getByRole("button", { name: "Complete Fit for Duty Check" }),
).toBeInTheDocument();
});
});
test("can bypass expired mode", async () => {
const view = render(
<Attestation
badge="123"
prefill={false}
onComplete={jest.fn()}
loading={false}
employees={EMPLOYEES}
certifications={CERTIFICATIONS_ONE_EXPIRED}
/>,
);
expect(view.getByText("Expired card")).toBeInTheDocument();
await userEvent.click(
view.getByRole("button", { name: "Continue to Fit for Duty Check →" }),
);
expect(
view.getByRole("button", { name: "Complete Fit for Duty Check" }),
).toBeInTheDocument();
});
});

@@ -216,4 +227,42 @@ describe("Attestation", () => {
expect(onComplete).not.toHaveBeenCalled();
jest.useRealTimers();
});

describe("expiry", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attestation.test.tsx now has a test for the expired case. But does it need a test that it shows the about to expire warning? I see that the details of that situation are asserted in expiry.test.tsx, but I'm thinking about a test that proves that CertificationBoxes is called.

return (
<WarningParagraph
className={className([
"border-0 light:bg-opacity-40 dark:bg-opacity-30 dark:text-white",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactoring suggestion: This is the only use of WarningParagraph, and these styles don't need to be controlled dynamically, so should they be part of WarningParagraph instead of passed in as an argument?

And should the background color be controlled by passing in "warning" or "error" instead of by giving an extraClassName?

Or, alternatively, WarningParagraph is just 2 divs and some styles, and it's not used anywhere else, so maybe it should be inlined here instead of abstracted?

<CertificateBox
now={now}
mode="error"
title={expired.length === 2 ? "Expired cards" : "Expired card"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: This is fragile if anybody ever has more than 2 certifications (could happen if we track more than two types, or if someone has certs on more than one rail line). It should be expired.length === 1 ? singular : plural or expired.length > 1 ? plural : singular.

Also applies to the previous call 10 lines above this.

<ol className="mb-4 ml-10 mr-0 mt-2 list-decimal">
<li className="m-0">
Take a picture of{" "}
{expireds.length === 2 ? "both cards" : "the card"}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if length > 2?

now: DateTime;
onContinue: () => void;
}): ReactElement => {
const expireds = certifications.filter((c) => isExpired(c, now));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this would actually cause problems, but it seems like we shouldn't have to recalculate which cards are expired at this point. The calculations are all in sync now but what if something changes? Perhaps the caller should calculate expireds once, and use the same value both for deciding whether to show Bypass and for passing in as an argument:

expireds = certifications.filter((c) => isExpired(c, now));
...
(expireds.length > 0) ? <Bypass expireds={expireds} displayName={...} onContinue={...} />

@@ -161,7 +176,6 @@ describe("OperatorSignInModal", () => {
await userEvent.type(view.getByRole("textbox"), "123");
await userEvent.click(view.getByRole("button", { name: "OK" }));

expect(view.getByText("Step 2 of 2")).toBeInTheDocument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these assertions removed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deploy-to-sandbox deploy this PR to sandbox
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants