diff --git a/frontend-react/.eslintrc.cjs b/frontend-react/.eslintrc.cjs index ab82b5316c1..36139517a94 100644 --- a/frontend-react/.eslintrc.cjs +++ b/frontend-react/.eslintrc.cjs @@ -112,6 +112,7 @@ module.exports = { /* Custom project rules */ "no-console": ["error", { allow: ["warn", "error", "info", "trace"] }], "@typescript-eslint/no-explicit-any": ["off"], + "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "error", { @@ -119,6 +120,8 @@ module.exports = { varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", }, ], "import/order": [ @@ -141,5 +144,6 @@ module.exports = { ], "sort-imports": ["error", { ignoreCase: true, ignoreDeclarationSort: true }], "@typescript-eslint/prefer-nullish-coalescing": ["error"], + "@typescript-eslint/no-empty-object-type": ["error", { allowInterfaces: "always" }], }, }; diff --git a/frontend-react/e2e/helpers/utils.ts b/frontend-react/e2e/helpers/utils.ts index 75570e0cb17..f425d48d1b6 100644 --- a/frontend-react/e2e/helpers/utils.ts +++ b/frontend-react/e2e/helpers/utils.ts @@ -115,7 +115,7 @@ export function fromDateWithTime(date: string, time?: string) { .substring(0, time.length - 2) .split(":") .map(Number); - hours = hours + (time.indexOf("pm") !== -1 ? 12 : 0); + hours = hours + (time.includes("pm") ? 12 : 0); fromDateTime.setHours(hours, minutes, 0, 0); } else { fromDateTime.setHours(0, 0, 0); @@ -132,7 +132,7 @@ export function toDateWithTime(date: string, time?: string) { .substring(0, time.length - 2) .split(":") .map(Number); - hours = hours + (time.indexOf("pm") !== -1 ? 12 : 0); + hours = hours + (time.includes("pm") ? 12 : 0); toDateTime.setHours(hours, minutes, 0, 0); } else { toDateTime.setHours(23, 59, 0); diff --git a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts index 34486c7505d..464e238ee77 100644 --- a/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts +++ b/frontend-react/e2e/spec/chromium-only/public-pages-link-check.spec.ts @@ -119,7 +119,7 @@ test.describe("Evaluate links on public facing pages", { tag: "@warning" }, () = } return { url, status: 200 }; - } catch (error) { + } catch (_error) { warnings.push({ url, message: "Internal link: Page error" }); return { url, status: 400 }; } finally { diff --git a/frontend-react/package.json b/frontend-react/package.json index a8f1a3d0391..eef72c2407c 100644 --- a/frontend-react/package.json +++ b/frontend-react/package.json @@ -144,8 +144,8 @@ "@types/react-router-dom": "^5.3.3", "@types/react-scroll-sync": "^0.9.0", "@types/sanitize-html": "^2.13.0", - "@typescript-eslint/eslint-plugin": "^7.17.0", - "@typescript-eslint/parser": "^7.17.0", + "@typescript-eslint/eslint-plugin": "^8.10.0", + "@typescript-eslint/parser": "^8.10.0", "@vitejs/plugin-react": "^4.3.3", "@vitest/coverage-istanbul": "^2.1.3", "@vitest/ui": "^2.1.3", @@ -187,8 +187,8 @@ "storybook": "^8.3.6", "storybook-addon-remix-react-router": "^3.0.1", "ts-node": "^10.9.2", - "tslib": "^2.6.3", - "typescript": "^5.5.4", + "tslib": "^2.8.0", + "typescript": "^5.6.3", "undici": "^6.20.1", "vite": "^5.4.9", "vite-plugin-checker": "^0.8.0", @@ -197,7 +197,9 @@ }, "resolutions": { "@types/react": "18.3.11", - "@okta/okta-auth-js": ">=7.8.1" + "@okta/okta-auth-js": ">=7.8.1", + "cookie": ">=0.7.0", + "send": ">=0.19.0" }, "engines": { "node": "^20.15" diff --git a/frontend-react/src/components/FileHandlers/FileHandlerFileUploadStep.test.tsx b/frontend-react/src/components/FileHandlers/FileHandlerFileUploadStep.test.tsx index 56340f66613..cffb8e2846c 100644 --- a/frontend-react/src/components/FileHandlers/FileHandlerFileUploadStep.test.tsx +++ b/frontend-react/src/components/FileHandlers/FileHandlerFileUploadStep.test.tsx @@ -2,14 +2,8 @@ import { fireEvent, screen, waitFor } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; import { Suspense } from "react"; -import FileHandlerFileUploadStep, { - getClientHeader, -} from "./FileHandlerFileUploadStep"; -import { - fakeFile, - mockSendFileWithErrors, - mockSendValidFile, -} from "../../__mocks__/validation"; +import FileHandlerFileUploadStep, { getClientHeader } from "./FileHandlerFileUploadStep"; +import { fakeFile, mockSendFileWithErrors, mockSendValidFile } from "../../__mocks__/validation"; import { sendersGenerator } from "../../__mockServers__/OrganizationMockServer"; import { RSSender } from "../../config/endpoints/settings"; import { UseSenderResourceHookResult } from "../../hooks/api/organizations/UseOrganizationSender/UseOrganizationSender"; @@ -19,11 +13,7 @@ import useAppInsightsContext from "../../hooks/UseAppInsightsContext/UseAppInsig import { INITIAL_STATE } from "../../hooks/UseFileHandler/UseFileHandler"; import { renderApp } from "../../utils/CustomRenderUtils"; import { MembershipSettings, MemberType } from "../../utils/OrganizationUtils"; -import { - CustomerStatus, - FileType, - Format, -} from "../../utils/TemporarySettingsAPITypes"; +import { CustomerStatus, FileType, Format } from "../../utils/TemporarySettingsAPITypes"; const { mockSessionContentReturnValue } = await vi.importMock< typeof import("../../contexts/Session/__mocks__/useSessionContext") @@ -42,9 +32,7 @@ describe("FileHandlerFileUploadStep", () => { }; const DEFAULT_SENDERS: RSSender[] = sendersGenerator(2); - function mockUseSenderResource( - result: Partial = {}, - ) { + function mockUseSenderResource(result: Partial = {}) { vi.spyOn(useSenderResourceExports, "default").mockReturnValue({ isInitialLoading: false, isLoading: false, @@ -80,14 +68,8 @@ describe("FileHandlerFileUploadStep", () => { test("renders the CSV-specific text", async () => { setup(); - await waitFor(() => - expect(screen.getByText("Upload CSV file")).toBeVisible(), - ); - expect( - screen.getByText( - "Make sure your file has a .csv extension", - ), - ).toBeVisible(); + await waitFor(() => expect(screen.getByText("Upload CSV file")).toBeVisible()); + expect(screen.getByText("Make sure your file has a .csv extension")).toBeVisible(); }); }); @@ -109,16 +91,8 @@ describe("FileHandlerFileUploadStep", () => { test("renders the HL7-specific text", async () => { setup(); - await waitFor(() => - expect( - screen.getByText("Upload HL7 v2.5.1 file"), - ).toBeVisible(), - ); - expect( - screen.getByText( - "Make sure your file has a .hl7 extension", - ), - ).toBeVisible(); + await waitFor(() => expect(screen.getByText("Upload HL7 v2.5.1 file")).toBeVisible()); + expect(screen.getByText("Make sure your file has a .hl7 extension")).toBeVisible(); }); }); @@ -140,20 +114,14 @@ describe("FileHandlerFileUploadStep", () => { ); await waitFor(async () => { - await userEvent.upload( - screen.getByTestId("file-input-input"), - fakeFile, - ); + await userEvent.upload(screen.getByTestId("file-input-input"), fakeFile); await new Promise((res) => setTimeout(res, 100)); }); } test("calls onFileChange with the file and content", async () => { await setup(); - expect(onFileChangeSpy).toHaveBeenCalledWith( - fakeFile, - "foo,bar\r\nbar,foo", - ); + expect(onFileChangeSpy).toHaveBeenCalledWith(fakeFile, "foo,bar\r\nbar,foo"); }); }); @@ -198,8 +166,7 @@ describe("FileHandlerFileUploadStep", () => { vi.spyOn(useWatersUploaderExports, "default").mockReturnValue({ isPending: false, error: null, - mutateAsync: async () => - await Promise.resolve(mockSendValidFile), + mutateAsync: async () => await Promise.resolve(mockSendValidFile), } as any); renderApp( @@ -214,12 +181,7 @@ describe("FileHandlerFileUploadStep", () => { }} fileContent="whatever" fileName="whatever.csv" - file={ - new File( - [new Blob(["whatever"])], - "whatever.csv", - ) - } + file={new File([new Blob(["whatever"])], "whatever.csv")} onFileSubmitSuccess={onFileSubmitSuccessSpy} onNextStepClick={onNextStepClickSpy} /> @@ -234,9 +196,7 @@ describe("FileHandlerFileUploadStep", () => { // eslint-disable-next-line testing-library/no-wait-for-side-effects fireEvent.submit(form); }); - await waitFor(() => - expect(onFileSubmitSuccessSpy).toHaveBeenCalled(), - ); + await waitFor(() => expect(onFileSubmitSuccessSpy).toHaveBeenCalled()); } afterEach(() => { @@ -245,9 +205,7 @@ describe("FileHandlerFileUploadStep", () => { test("it calls onFileSubmitSuccess with the response", async () => { await setup(); - expect(onFileSubmitSuccessSpy).toHaveBeenCalledWith( - mockSendValidFile, - ); + expect(onFileSubmitSuccessSpy).toHaveBeenCalledWith(mockSendValidFile); }); test("it calls onNextStepClick", async () => { @@ -280,6 +238,7 @@ describe("FileHandlerFileUploadStep", () => { isPending: false, error: null, mutateAsync: async () => + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors await Promise.reject({ data: mockSendFileWithErrors, }), @@ -296,12 +255,7 @@ describe("FileHandlerFileUploadStep", () => { }} fileContent="whatever" fileName="whatever.csv" - file={ - new File( - [new Blob(["whatever"])], - "whatever.csv", - ) - } + file={new File([new Blob(["whatever"])], "whatever.csv")} onFileSubmitError={onFileSubmitErrorSpy} /> , @@ -315,9 +269,7 @@ describe("FileHandlerFileUploadStep", () => { // eslint-disable-next-line testing-library/no-wait-for-side-effects fireEvent.submit(form); }); - await waitFor(() => - expect(onFileSubmitErrorSpy).toHaveBeenCalled(), - ); + await waitFor(() => expect(onFileSubmitErrorSpy).toHaveBeenCalled()); } afterEach(() => { @@ -373,47 +325,27 @@ describe("getClientHeader", () => { describe("when selectedSchemaName is falsy", () => { test("returns an empty string", () => { - expect( - getClientHeader( - undefined, - DEFAULT_ACTIVE_MEMBERSHIP, - DEFAULT_SENDER, - ), - ).toEqual(""); + expect(getClientHeader(undefined, DEFAULT_ACTIVE_MEMBERSHIP, DEFAULT_SENDER)).toEqual(""); }); }); describe("when activeMembership is falsy", () => { test("returns an empty string", () => { - expect( - getClientHeader(DEFAULT_SCHEMA_NAME, undefined, DEFAULT_SENDER), - ).toEqual(""); - expect( - getClientHeader(DEFAULT_SCHEMA_NAME, null, DEFAULT_SENDER), - ).toEqual(""); + expect(getClientHeader(DEFAULT_SCHEMA_NAME, undefined, DEFAULT_SENDER)).toEqual(""); + expect(getClientHeader(DEFAULT_SCHEMA_NAME, null, DEFAULT_SENDER)).toEqual(""); }); }); describe("when sender is falsy", () => { test("returns an empty string", () => { - expect( - getClientHeader( - DEFAULT_SCHEMA_NAME, - DEFAULT_ACTIVE_MEMBERSHIP, - undefined, - ), - ).toEqual(""); + expect(getClientHeader(DEFAULT_SCHEMA_NAME, DEFAULT_ACTIVE_MEMBERSHIP, undefined)).toEqual(""); }); }); describe("when activeMembership.parsedName is falsy", () => { test("returns an empty string", () => { expect( - getClientHeader( - DEFAULT_SCHEMA_NAME, - { ...DEFAULT_ACTIVE_MEMBERSHIP, parsedName: "" }, - DEFAULT_SENDER, - ), + getClientHeader(DEFAULT_SCHEMA_NAME, { ...DEFAULT_ACTIVE_MEMBERSHIP, parsedName: "" }, DEFAULT_SENDER), ).toEqual(""); }); }); @@ -421,36 +353,22 @@ describe("getClientHeader", () => { describe("when activeMembership.service is falsy", () => { test("returns an empty string", () => { expect( - getClientHeader( - DEFAULT_SCHEMA_NAME, - { ...DEFAULT_ACTIVE_MEMBERSHIP, service: "" }, - DEFAULT_SENDER, - ), + getClientHeader(DEFAULT_SCHEMA_NAME, { ...DEFAULT_ACTIVE_MEMBERSHIP, service: "" }, DEFAULT_SENDER), ).toEqual(""); }); }); describe("when selected schema value matches sender's schema", () => { test("returns the client value from the organization's parsed name and service", () => { - expect( - getClientHeader( - DEFAULT_SCHEMA_NAME, - DEFAULT_ACTIVE_MEMBERSHIP, - DEFAULT_SENDER, - ), - ).toEqual("orgName.serviceName"); + expect(getClientHeader(DEFAULT_SCHEMA_NAME, DEFAULT_ACTIVE_MEMBERSHIP, DEFAULT_SENDER)).toEqual( + "orgName.serviceName", + ); }); }); describe("when selected schema value does not match the sender's schema", () => { test("returns an empty string", () => { - expect( - getClientHeader( - "bogus-schema", - DEFAULT_ACTIVE_MEMBERSHIP, - DEFAULT_SENDER, - ), - ).toEqual(""); + expect(getClientHeader("bogus-schema", DEFAULT_ACTIVE_MEMBERSHIP, DEFAULT_SENDER)).toEqual(""); }); }); }); diff --git a/frontend-react/src/components/MessageTracker/MessageReceivers.tsx b/frontend-react/src/components/MessageTracker/MessageReceivers.tsx index 72574f51758..d4a8b268dc0 100644 --- a/frontend-react/src/components/MessageTracker/MessageReceivers.tsx +++ b/frontend-react/src/components/MessageTracker/MessageReceivers.tsx @@ -43,17 +43,15 @@ export const ColumnDataTitles = { } as const satisfies { [k in keyof NormalizedReceiverData]: string; }; -export type ColumnDataTitle = - (typeof ColumnDataTitles)[keyof typeof ColumnDataTitles]; +export type ColumnDataTitle = (typeof ColumnDataTitles)[keyof typeof ColumnDataTitles]; export type NormalizedReceiverKey = keyof typeof ColumnDataTitles; -const FilterOptionsEnum = { +export const FilterOptionsEnum = { NONE: "none", ASC: "asc", DESC: "desc", } as const; -export type FilterOption = - (typeof FilterOptionsEnum)[keyof typeof FilterOptionsEnum]; +export type FilterOption = (typeof FilterOptionsEnum)[keyof typeof FilterOptionsEnum]; export const StatusEnum = { BATCH: "batch", @@ -91,22 +89,14 @@ export const MessageReceivers = ({ receiverDetails }: MessageReceiverProps) => { columnKey: "fileLocationMain", columnHeader: "Main", content: (() => { - const status = parseFileLocation( - row?.fileUrl ?? NO_DATA_STRING, - ).folderLocation; + const status = parseFileLocation(row?.fileUrl ?? NO_DATA_STRING).folderLocation; return (

{status.toLocaleUpperCase()}

@@ -116,18 +106,14 @@ export const MessageReceivers = ({ receiverDetails }: MessageReceiverProps) => { { columnKey: "fileLocationSub", columnHeader: "Sub", - content: parseFileLocation(row?.fileUrl ?? NO_DATA_STRING) - .sendingOrg, + content: parseFileLocation(row?.fileUrl ?? NO_DATA_STRING).sendingOrg, }, { columnKey: "fileLocationFileName", columnHeader: "File Name", content: ( - { - parseFileLocation(row?.fileUrl ?? NO_DATA_STRING) - .fileName - } + {parseFileLocation(row?.fileUrl ?? NO_DATA_STRING).fileName} ), }, diff --git a/frontend-react/src/components/USLink.tsx b/frontend-react/src/components/USLink.tsx index 40f22d9c57b..75cde84f143 100644 --- a/frontend-react/src/components/USLink.tsx +++ b/frontend-react/src/components/USLink.tsx @@ -2,12 +2,7 @@ import { IEventTelemetry } from "@microsoft/applicationinsights-web"; import { ButtonProps } from "@trussworks/react-uswds/lib/components/Button/Button"; import classnames from "classnames"; import DOMPurify from "dompurify"; -import { - AnchorHTMLAttributes, - MouseEvent as ReactMouseEvent, - ReactNode, - useMemo, -} from "react"; +import { AnchorHTMLAttributes, MouseEvent as ReactMouseEvent, ReactNode, useMemo } from "react"; import { Link, NavLink, useLocation } from "react-router-dom"; import useAppInsightsContext from "../hooks/UseAppInsightsContext/UseAppInsightsContext"; @@ -20,10 +15,8 @@ interface CustomLinkProps { activeClassName?: string; state?: any; } -type USLinkProps = AnchorHTMLAttributes & - Omit; -type USNavLinkProps = Pick, "href"> & - CustomLinkProps; +type USLinkProps = AnchorHTMLAttributes & Omit; +type USNavLinkProps = Pick, "href"> & CustomLinkProps; /** * Stateless function to get route href from href that could be @@ -39,15 +32,10 @@ export function getHrefRoute(href?: string): string | undefined { if (href === undefined) return undefined; try { - const url = new URL( - href.replace(/^\/\//, `${window.location.protocol}//`), - ); - if ( - url.protocol.startsWith("http") && - url.origin === window.location.origin - ) + const url = new URL(href.replace(/^\/\//, `${window.location.protocol}//`)); + if (url.protocol.startsWith("http") && url.origin === window.location.origin) return `${url.pathname}${url.search}`; - } catch (e: any) { + } catch (_e: any) { return href; } @@ -66,12 +54,7 @@ const sanitizeHref = (href: string | undefined) => { * Sanitizes href and determines if href is an app route or regular * link. */ -export const SafeLink = ({ - children, - href, - state, - ...anchorHTMLAttributes -}: SafeLinkProps) => { +export const SafeLink = ({ children, href, state, ...anchorHTMLAttributes }: SafeLinkProps) => { const sanitizedHref = sanitizeHref(href); const routeHref = getHrefRoute(sanitizedHref); const isFile = sanitizedHref?.startsWith("/assets/"); @@ -102,9 +85,7 @@ export const USLink = ({ children, className, ...props }: USLinkProps) => { ); }; -export interface USLinkButtonProps - extends USLinkProps, - Omit {} +export interface USLinkButtonProps extends USLinkProps, Omit {} export const USLinkButton = ({ className, @@ -131,9 +112,7 @@ export const USLinkButton = ({ className, ); if (isExternalUrl(sanitizeHref(anchorHTMLAttributes.href))) { - return ( - - ); + return ; } return ; }; @@ -151,11 +130,7 @@ export const USLinkButton = ({ * My Site * * */ -export const USExtLink = ({ - className, - children, - ...anchorHTMLAttributes -}: Omit) => { +export const USExtLink = ({ className, children, ...anchorHTMLAttributes }: Omit) => { return ( ( - +export const USCrumbLink = ({ className, children, ...anchorHTMLAttributes }: USLinkProps) => ( + {children} ); @@ -186,13 +154,7 @@ export const USCrumbLink = ({ /** A single link to replace NavLink (react-router-dom). Applies uswds navigation link styling * and handles both active and standard style states. This DOES NOT use `USLink` as a base; it * relies on `NavLink` for additional functionality. */ -export const USNavLink = ({ - href, - children, - className, - activeClassName, - ...props -}: USNavLinkProps) => { +export const USNavLink = ({ href, children, className, activeClassName, ...props }: USNavLinkProps) => { const { hash: currentHash } = useLocation(); const hashIndex = href?.indexOf("#") ?? -1; const hash = hashIndex > -1 ? href?.slice(hashIndex) : ""; @@ -202,8 +164,7 @@ export const USNavLink = ({ to={href ?? ""} className={({ isActive: isPathnameActive }) => { // Without this, all hash links would be considered active for a path - const isActive = - isPathnameActive && (hash === "" || currentHash === hash); + const isActive = isPathnameActive && (hash === "" || currentHash === hash); return classnames("usa-nav__link", { "usa-current": isActive, @@ -227,31 +188,21 @@ export function isExternalUrl(href?: string) { if (href === undefined) return false; try { // Browsers allow // shorthand in anchor urls but URL does not - const url = new URL( - href.replace(/^\/\//, `${window.location.protocol}//`), - ); + const url = new URL(href.replace(/^\/\//, `${window.location.protocol}//`)); return ( - (url.protocol.startsWith("http") && - url.host !== "cdc.gov" && - !url.host.endsWith(".cdc.gov")) || + (url.protocol.startsWith("http") && url.host !== "cdc.gov" && !url.host.endsWith(".cdc.gov")) || href.startsWith("mailto:") ); - } catch (e: any) { + } catch (_e: any) { return false; } } -export interface USSmartLinkProps - extends AnchorHTMLAttributes { +export interface USSmartLinkProps extends AnchorHTMLAttributes { trackClick?: IEventTelemetry; } -export function USSmartLink({ - children, - onClick, - trackClick, - ...props -}: USSmartLinkProps) { +export function USSmartLink({ children, onClick, trackClick, ...props }: USSmartLinkProps) { const appInsights = useAppInsightsContext(); let isExternal = props.href !== undefined; const finalOnClick = useMemo( diff --git a/frontend-react/src/config/endpoints/index.ts b/frontend-react/src/config/endpoints/index.ts index 73a26efb994..f4acce2810f 100644 --- a/frontend-react/src/config/endpoints/index.ts +++ b/frontend-react/src/config/endpoints/index.ts @@ -45,7 +45,7 @@ export class RSEndpoint { } get hasDynamicSegments(): boolean { - return this.path.indexOf("/:") > -1; + return this.path.includes("/:"); } // replaces dynamic paths (`/:` prefixed segments) in an endpoint path @@ -54,30 +54,23 @@ export class RSEndpoint { // would return `/world` toDynamicUrl(segments?: StringIndexed) { if (!segments && this.hasDynamicSegments) { - throw new Error( - `Attempted to use dynamic url without providing segment values: ${this.path}`, - ); + throw new Error(`Attempted to use dynamic url without providing segment values: ${this.path}`); } if (!segments) { return this.url; } const pathWithSegments = Object.entries(segments).reduce( - (pathWithSegments, [segmentKey, segmentValue]) => - pathWithSegments.replace(`:${segmentKey}`, segmentValue), + (pathWithSegments, [segmentKey, segmentValue]) => pathWithSegments.replace(`:${segmentKey}`, segmentValue), this.url, ); - if (pathWithSegments.indexOf("/:") > -1) { - throw new Error( - `missing dynamic path param: ${this.url}, ${JSON.stringify(segments)}`, - ); + if (pathWithSegments.includes("/:")) { + throw new Error(`missing dynamic path param: ${this.url}, ${JSON.stringify(segments)}`); } return pathWithSegments; } // return the complete params that will be passed to axios to make a specific call to this endpoint - toAxiosConfig( - requestOptions: Partial, - ): Partial { + toAxiosConfig(requestOptions: Partial): Partial { const dynamicUrl = this.toDynamicUrl(requestOptions.segments); return { ...omit(requestOptions, "segments"), // this is yucky but necessary for now diff --git a/frontend-react/src/contexts/Session/SessionProvider.tsx b/frontend-react/src/contexts/Session/SessionProvider.tsx index 8ae4dcfca2e..35033e847aa 100644 --- a/frontend-react/src/contexts/Session/SessionProvider.tsx +++ b/frontend-react/src/contexts/Session/SessionProvider.tsx @@ -1,20 +1,7 @@ -import { - AuthState, - CustomUserClaims, - OktaAuth, - UserClaims, -} from "@okta/okta-auth-js"; +import { AuthState, CustomUserClaims, OktaAuth, UserClaims } from "@okta/okta-auth-js"; import { useOktaAuth } from "@okta/okta-react"; import axios, { AxiosError } from "axios"; -import { - createContext, - PropsWithChildren, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import { createContext, PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { IIdleTimerProps, useIdleTimer } from "react-idle-timer"; import type { AppConfig } from "../../config"; @@ -24,16 +11,8 @@ import useAppInsightsContext from "../../hooks/UseAppInsightsContext/UseAppInsig import { updateApiSessions } from "../../network/Apis"; import { EventName } from "../../utils/AppInsights"; import { isUseragentPreferred } from "../../utils/BrowserUtils"; -import { - MembershipSettings, - membershipsFromToken, - MemberType, - RSUserClaims, -} from "../../utils/OrganizationUtils"; -import { - getUserPermissions, - RSUserPermissions, -} from "../../utils/PermissionsUtils"; +import { MembershipSettings, membershipsFromToken, MemberType, RSUserClaims } from "../../utils/OrganizationUtils"; +import { getUserPermissions, RSUserPermissions } from "../../utils/PermissionsUtils"; import { RSConsole } from "../../utils/rsConsole/rsConsole"; import { RSNetworkError } from "../../utils/RSNetworkError"; @@ -52,10 +31,7 @@ export interface RSSessionContext { config: AppConfig; site: typeof site; rsConsole: RSConsole; - authorizedFetch: ( - options: Partial, - EndpointConfig?: RSEndpoint, - ) => Promise; + authorizedFetch: (options: Partial, EndpointConfig?: RSEndpoint) => Promise; } export const SessionContext = createContext(null as any); @@ -82,12 +58,9 @@ export async function staticAuthorizedFetch({ options, endpointConfig, }: StaticAuthorizedFetchProps) { - if (options.segments && !endpointConfig) - throw new Error("EndpointConfig required when using segments"); - if (options.url && endpointConfig) - throw new Error("Cannot use both url and EndpointConfig"); - if (!options.url && !endpointConfig) - throw new Error("Must use either url or EndpointConfig"); + if (options.segments && !endpointConfig) throw new Error("EndpointConfig required when using segments"); + if (options.url && endpointConfig) throw new Error("Cannot use both url and EndpointConfig"); + if (!options.url && !endpointConfig) throw new Error("Must use either url or EndpointConfig"); const headerOverrides = options?.headers ?? {}; @@ -124,29 +97,16 @@ export async function staticAuthorizedFetch({ } } -function SessionProvider({ - children, - config, - rsConsole, -}: SessionProviderProps) { +function SessionProvider({ children, config, rsConsole }: SessionProviderProps) { const { authState, oktaAuth } = useOktaAuth(); const aiReactPlugin = useAppInsightsContext(); - const initActiveMembership = useRef( - JSON.parse( - sessionStorage.getItem("__deprecatedActiveMembership") ?? "null", - ), - ); - const [_activeMembership, setActiveMembership] = useState( - initActiveMembership.current, - ); + const initActiveMembership = useRef(JSON.parse(sessionStorage.getItem("__deprecatedActiveMembership") ?? "null")); + const [_activeMembership, setActiveMembership] = useState(initActiveMembership.current); const activeMembership = useMemo(() => { - const actualMembership = membershipsFromToken( - authState?.accessToken?.claims, - ); + const actualMembership = membershipsFromToken(authState?.accessToken?.claims); - if (actualMembership == null || !authState?.isAuthenticated) - return undefined; + if (actualMembership == null || !authState?.isAuthenticated) return undefined; return { ...actualMembership, ...(_activeMembership ?? {}) }; }, [authState, _activeMembership]); @@ -161,9 +121,8 @@ function SessionProvider({ } }, [oktaAuth, rsConsole]); - const handleIdle = useCallback< - Exclude - >( + const handleIdle = useCallback>( + // eslint-disable-next-line @typescript-eslint/no-misused-promises async (_event, _timer) => { if (await oktaAuth.isAuthenticated()) { aiReactPlugin.trackEvent({ @@ -186,11 +145,7 @@ function SessionProvider({ const sessionStartTime = useRef(new Date().getTime()); const sessionTimeAggregate = useRef(0); const calculateAggregateTime = () => { - return ( - new Date().getTime() - - sessionStartTime.current + - sessionTimeAggregate.current - ); + return new Date().getTime() - sessionStartTime.current + sessionTimeAggregate.current; }; // do best-attempt window tracking @@ -250,13 +205,10 @@ function SessionProvider({ activeMembership, user: { claims: authState?.idToken?.claims, - ...getUserPermissions( - authState?.accessToken?.claims as RSUserClaims, - ), + ...getUserPermissions(authState?.accessToken?.claims as RSUserClaims), /* This logic is a for when admins have other orgs present on their Okta claims * that interfere with the activeMembership.memberType "soft" check */ - isAdminStrictCheck: - activeMembership?.memberType === MemberType.PRIME_ADMIN, + isAdminStrictCheck: activeMembership?.memberType === MemberType.PRIME_ADMIN, }, logout, _activeMembership, @@ -266,16 +218,7 @@ function SessionProvider({ rsConsole, authorizedFetch, }; - }, [ - oktaAuth, - authState, - activeMembership, - logout, - _activeMembership, - config, - rsConsole, - authorizedFetch, - ]); + }, [oktaAuth, authState, activeMembership, logout, _activeMembership, config, rsConsole, authorizedFetch]); useEffect(() => { updateApiSessions({ @@ -293,10 +236,7 @@ function SessionProvider({ sessionStorage.removeItem("__deprecatedActiveMembership"); sessionStorage.removeItem("__deprecatedFetchInit"); } else { - sessionStorage.setItem( - "__deprecatedActiveMembership", - JSON.stringify(activeMembership), - ); + sessionStorage.setItem("__deprecatedActiveMembership", JSON.stringify(activeMembership)); sessionStorage.setItem( "__deprecatedFetchInit", JSON.stringify({ @@ -315,19 +255,13 @@ function SessionProvider({ // keep auth user up-to-date useEffect(() => { - if ( - authState?.idToken?.claims.email && - !aiReactPlugin.properties.context.user.authenticatedId - ) { + if (authState?.idToken?.claims.email && !aiReactPlugin.properties.context.user.authenticatedId) { aiReactPlugin.properties.context.user.setAuthenticatedUserContext( authState.idToken.claims.email, undefined, true, ); - } else if ( - !authState?.idToken?.claims.email && - aiReactPlugin.properties.context.user.authenticatedId - ) { + } else if (!authState?.idToken?.claims.email && aiReactPlugin.properties.context.user.authenticatedId) { aiReactPlugin.properties.context.user.clearAuthenticatedUserContext(); } }, [authState?.idToken, aiReactPlugin]); @@ -341,11 +275,7 @@ function SessionProvider({ if (!authState) return null; - return ( - - {children} - - ); + return {children}; } export default SessionProvider; diff --git a/frontend-react/src/hooks/UseFileHandler/UseFileHandler.ts b/frontend-react/src/hooks/UseFileHandler/UseFileHandler.ts index 0e86dce3286..86119581012 100644 --- a/frontend-react/src/hooks/UseFileHandler/UseFileHandler.ts +++ b/frontend-react/src/hooks/UseFileHandler/UseFileHandler.ts @@ -66,10 +66,7 @@ export interface FileHandlerAction { payload?: FileHandlerActionPayload; // reset actions will have no payload } -type FileHandlerReducer = ( - state: FileHandlerState, - action: FileHandlerAction, -) => FileHandlerState; +type FileHandlerReducer = (state: FileHandlerState, action: FileHandlerAction) => FileHandlerState; export const INITIAL_STATE = { fileInputResetValue: 0, @@ -109,10 +106,7 @@ function getPreSubmitState(): Partial { } // update state when file is selected in form -function calculateFileSelectedState( - state: FileHandlerState, - payload: FileSelectedPayload, -): Partial { +function calculateFileSelectedState(state: FileHandlerState, payload: FileSelectedPayload): Partial { const { file, fileContent } = payload; let uploadType; if (file.type) { @@ -124,11 +118,7 @@ function calculateFileSelectedState( uploadType = fileNameArray[fileNameArray.length - 1]; } - if ( - uploadType !== "text/csv" && - uploadType !== "csv" && - uploadType !== "hl7" - ) { + if (uploadType !== "text/csv" && uploadType !== "csv" && uploadType !== "hl7") { return { ...state, localError: "The file type must be .csv or .hl7", @@ -145,12 +135,9 @@ function calculateFileSelectedState( // previously loading file contents here // since this is an async action we'll do this in the calling component // prior to dispatching into the reducer, and handle the file content in local state - const contentType = - uploadType === "csv" || uploadType === "text/csv" - ? ContentType.CSV - : ContentType.HL7; + const contentType = uploadType === "csv" || uploadType === "text/csv" ? ContentType.CSV : ContentType.HL7; - const fileType = uploadType.match("hl7") ? FileType.HL7 : FileType.CSV; + const fileType = /hl7/.exec(uploadType) ? FileType.HL7 : FileType.CSV; return { ...state, file, @@ -168,20 +155,10 @@ export function calculateRequestCompleteState( payload: RequestCompletePayload, ): Partial { const { - response: { - destinations, - id, - timestamp, - errors, - status, - warnings, - overallStatus, - }, + response: { destinations, id, timestamp, errors, status, warnings, overallStatus }, } = payload; - const destinationList = destinations?.length - ? destinations.map((d: Destination) => d.organization).join(", ") - : ""; + const destinationList = destinations?.length ? destinations.map((d: Destination) => d.organization).join(", ") : ""; return { destinations: destinationList, @@ -198,10 +175,7 @@ export function calculateRequestCompleteState( }; } -function reducer( - state: FileHandlerState, - action: FileHandlerAction, -): FileHandlerState { +function reducer(state: FileHandlerState, action: FileHandlerAction): FileHandlerState { const { type, payload } = action; switch (type) { case FileHandlerActionType.RESET: @@ -214,17 +188,11 @@ function reducer( return { ...state, ...preSubmitState }; } case FileHandlerActionType.FILE_SELECTED: { - const fileSelectedState = calculateFileSelectedState( - state, - payload as FileSelectedPayload, - ); + const fileSelectedState = calculateFileSelectedState(state, payload as FileSelectedPayload); return { ...state, ...fileSelectedState }; } case FileHandlerActionType.REQUEST_COMPLETE: { - const requestCompleteState = calculateRequestCompleteState( - state, - payload as RequestCompletePayload, - ); + const requestCompleteState = calculateRequestCompleteState(state, payload as RequestCompletePayload); return { ...state, ...requestCompleteState }; } case FileHandlerActionType.SCHEMA_SELECTED: { @@ -262,10 +230,7 @@ export interface UseFileHandlerHookResult { // the pattern laid down in UsePagination for now, in case we need to make this more // complex later - DWS export default function useFileHandler(): UseFileHandlerHookResult { - const [state, dispatch] = useReducer( - reducer, - getInitialState(), - ); + const [state, dispatch] = useReducer(reducer, getInitialState()); /* TODO: possible future refactors: - we could abstract over the dispatch function as UsePagination does and expose individual diff --git a/frontend-react/src/hooks/api/UseReportHistory/UseReportHistory.ts b/frontend-react/src/hooks/api/UseReportHistory/UseReportHistory.ts index 2a63dc47cb7..b550dd8dd8c 100644 --- a/frontend-react/src/hooks/api/UseReportHistory/UseReportHistory.ts +++ b/frontend-react/src/hooks/api/UseReportHistory/UseReportHistory.ts @@ -2,7 +2,6 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import { RSOrganizationSettings } from "../../../config/endpoints/settings"; import useSessionContext from "../../../contexts/Session/useSessionContext"; -// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface RSReportHistorySearchParams {} export interface RSReportAction { diff --git a/frontend-react/src/pages/admin/AdminRevHistory.test.tsx b/frontend-react/src/pages/admin/AdminRevHistory.test.tsx index 3a45bab4c8e..30fe9cc7d5b 100644 --- a/frontend-react/src/pages/admin/AdminRevHistory.test.tsx +++ b/frontend-react/src/pages/admin/AdminRevHistory.test.tsx @@ -2,10 +2,7 @@ import { screen } from "@testing-library/react"; import { userEvent } from "@testing-library/user-event"; import { _exportForTesting } from "./AdminRevHistory"; -import { - RSSettingRevision, - RSSettingRevisionParams, -} from "../../hooks/api/UseSettingsRevisions/UseSettingsRevisions"; +import { RSSettingRevision, RSSettingRevisionParams } from "../../hooks/api/UseSettingsRevisions/UseSettingsRevisions"; import { renderApp } from "../../utils/CustomRenderUtils"; const fakeRows: RSSettingRevision[] = [ @@ -100,30 +97,24 @@ describe("AdminRevHistory", () => { // make sure the meta data at the bottom is updated. { - const leftMetaText = - screen.getByTestId("meta-left-data").textContent; + const leftMetaText = screen.getByTestId("meta-left-data").textContent; expect(leftMetaText).toBe("Flags: isDeleted: true isActive: false"); } { - const rightMetaText = - screen.getByTestId("meta-right-data").textContent; - expect(rightMetaText).toBe( - "Flags: isDeleted: false isActive: false", - ); + const rightMetaText = screen.getByTestId("meta-right-data").textContent; + expect(rightMetaText).toBe("Flags: isDeleted: false isActive: false"); } // look for the unique "Description" text in each diff. { - const leftDiffText = - screen.getByTestId("left-compare-text").textContent ?? ""; - expect(/ORIGINAL/.test(leftDiffText)).toBe(true); - expect(/FIRST_REVISION/.test(leftDiffText)).toBe(false); + const leftDiffText = screen.getByTestId("left-compare-text").textContent ?? ""; + expect(leftDiffText.includes("ORIGINAL")).toBe(true); + expect(leftDiffText.includes("FIRST_REVISION")).toBe(false); } { - const rightDiffText = - screen.getByTestId("right-compare-text").textContent ?? ""; - expect(/ORIGINAL/.test(rightDiffText)).toBe(false); - expect(/FIRST_REVISION/.test(rightDiffText)).toBe(true); + const rightDiffText = screen.getByTestId("right-compare-text").textContent ?? ""; + expect(rightDiffText.includes("ORIGINAL")).toBe(false); + expect(rightDiffText.includes("FIRST_REVISION")).toBe(true); } }); }); diff --git a/frontend-react/src/pages/misc/FeatureFlags.tsx b/frontend-react/src/pages/misc/FeatureFlags.tsx index 0b34879aba7..4fadebb53bc 100644 --- a/frontend-react/src/pages/misc/FeatureFlags.tsx +++ b/frontend-react/src/pages/misc/FeatureFlags.tsx @@ -1,11 +1,4 @@ -import { - Alert, - Button, - Grid, - GridContainer, - Label, - TextInput, -} from "@trussworks/react-uswds"; +import { Alert, Button, Grid, GridContainer, Label, TextInput } from "@trussworks/react-uswds"; import { useCallback, useRef } from "react"; import { Helmet } from "react-helmet-async"; @@ -40,10 +33,7 @@ export function FeatureFlagsPage() { if (newFlagInputText.current?.value) { newFlagInputText.current.value = ""; } - showToast( - `Feature flag '${newFlag}' added. You will now see UI related to this feature.`, - "success", - ); + showToast(`Feature flag '${newFlag}' added. You will now see UI related to this feature.`, "success"); }, [newFlagInputText, checkFlags, dispatch]); const deleteFlagClick = useCallback( (flagname: string) => { @@ -64,10 +54,7 @@ export function FeatureFlagsPage() {

List of feature flags

-