Skip to content

Commit

Permalink
set ecr viewer path permanently (#3053)
Browse files Browse the repository at this point in the history
* permanently set base path in ecr-viewer

* Rename middleware to be a .ts file

* replace a tags with Next's Link tag

* remove title from Header link

* retype base_path to be a string

* fix metrics test

* update header snapshot test

* address middleware tests

* add back button to retrieval failed

* remove unused rewrite in next config

* update matcher

* Prevent access to /api/fhir-data

* [pre-commit.ci] auto fixes from pre-commit hooks

* fix url in readme

* remove accident commited next-runtime-env

* [pre-commit.ci] auto fixes from pre-commit hooks

* Filter by conditions in eCR Library (frontend) (#2981)

* First pass, filter conditions functionality

* add select/deselect all functionality

* update some styling, maintain checkbox state when toggling filter button

* styling updates, wip

* checkbox color, add icon, add uswds sprite.svg to assets

* adjust padding to fix checkbox focus ring cut off

* fix icon not displaying by adding static file route

* fix unintentional scrolling bug

* update filter row top border

* wip, add comments, decompose conditions filter to separate const

* fix scrolling bug by adding position-relative

* add snapshot and unit tests

* add JSDocs

* remove css classes and use utilities instead

* update snapshot test

* update select all/deselect all functionality s.t. default is all conditions checked, update tests

* update so that filters reset if clicking off filter before clicking the Apply button, add tests

* update basepath so it works in prod

* update tests

* update styles in diff button states, update icon size, make capitalization consistent

* Remove log

Co-authored-by: Mary McGrath <[email protected]>

* use as form/fieldset, update sync state bug, update tests

* remove manual checkboxing for select all, lets react handle the render

* rework state management, update tests

* code review changes, minor

* query should persist over a reload

* update backend so default (all conditions) would leave out condition param from URL query, add/update tests

* use import for icon

* Update base_path env var name

Co-authored-by: Boban <[email protected]>

* update snapshot test

* re-use resetFilterConditions

* one more nit

* update ecr library height to accommodate fiter bar

* update env var name for base path

---------

Co-authored-by: Mary McGrath <[email protected]>
Co-authored-by: Boban <[email protected]>

* fix base path for filters

* [pre-commit.ci] auto fixes from pre-commit hooks

* update filter test

* Update middleware.ts

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Angela The <[email protected]>
Co-authored-by: Mary McGrath <[email protected]>
  • Loading branch information
4 people authored Dec 19, 2024
1 parent 7b7e8be commit 77db6a9
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 42 deletions.
14 changes: 8 additions & 6 deletions containers/ecr-viewer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ To build the Docker image for the eCR Viewer from source instead of downloading
3. Navigate to `/phdi/containers/ecr-viewer/`.
4. Run `docker build -t ecr-viewer .`.

## Non Integrated Viewer
## Non Integrated Viewer

To enable the Non Integrated Viewer homepage, set the environment variable `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` equal to `true`. This will enable the Non Integrated viewer homepage at `localhost:3000`.
To enable the Non Integrated Viewer homepage, set the environment variable `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` equal to `true`. This will enable the Non Integrated viewer homepage at `localhost:3000/ecr-viewer`.

For local development, if `NEXT_PUBLIC_NON_INTEGRATED_VIEWER` is not set equal to `true` on `.env.local`, convert-seed-data will not seed the metadata.

Expand All @@ -50,11 +50,12 @@ If you have problems connecting to your database, use this command to see what o

If building the Docker Image doesn't work as expected, try to first run the eCR Viewer locally using the steps below.

If you consistently encounter the error message `"ecr_viewer_db" does not exist` when attempting to run the app, there could be conflicting databases running on port 5432 as part of other background processes. Try pruning any dangling Docker images and containers (`docker image prune` and `docker container prune`). If issues persist, try logging into `psql` on the command line to see what databases are running there.
If you consistently encounter the error message `"ecr_viewer_db" does not exist` when attempting to run the app, there could be conflicting databases running on port 5432 as part of other background processes. Try pruning any dangling Docker images and containers (`docker image prune` and `docker container prune`). If issues persist, try logging into `psql` on the command line to see what databases are running there.

## Development

### Run eCR Viewer Locally

To run the eCR Viewer locally:

1. Ensure that Git, Docker, and Node (version 18.x or higher) are installed.
Expand All @@ -63,7 +64,7 @@ To run the eCR Viewer locally:
4. Install all of the Node dependencies for the eCR Viewer with `npm install`.
5. Setup your `.env.local` by running `npm run setup-local-env`.
6. Create seed data with `npm run convert-seed-data` - this will take ~10 minutes. Note that this process will fail immediately if the Docker daemon isn't running.
7. Run the eCR Viewer on `localhost:3000` with `npm run local-dev`.
7. Run the eCR Viewer on `localhost:3000/ecr-viewer` with `npm run local-dev`.

### Windows Setup

Expand All @@ -82,8 +83,8 @@ Sample eICRs are included in `containers/ecr-viewer/seed-scripts/baseECR/`. If y
Additional commands can be found in [`package.json`](package.json) and are also made available in [`Makefile`](Makefile).

## API Documentation
Can be found in [api-documentation.md](api-documentation.md).

Can be found in [api-documentation.md](api-documentation.md).

# Architecture Diagram

Expand All @@ -105,7 +106,7 @@ flowchart LR
end
end
subgraph service[REST API Service]
direction TB
subgraph mr["fab:fa-docker container"]
Expand Down Expand Up @@ -135,6 +136,7 @@ ecr ===> mr ===> post-ecr
```

#### Application API

```mermaid
graph TD
A[ecr-viewer]
Expand Down
1 change: 1 addition & 0 deletions containers/ecr-viewer/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace NodeJS {
AWS_SECRET_ACCESS_KEY: string;
AZURE_CONTAINER_NAME: string;
AZURE_STORAGE_CONNECTION_STRING: string;
BASE_PATH: string;
DATABASE_TYPE: string;
DATABASE_URL: string;
ECR_BUCKET_NAME: string;
Expand Down
10 changes: 1 addition & 9 deletions containers/ecr-viewer/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ const nextConfig = {
instrumentationHook: true, // this needs to be here for opentelemetry
},
transpilePackages: ["yaml"],
async rewrites() {
return [
{
source: "/ecr-viewer/:slug*",
destination: "/:slug*",
},
];
},
output: "standalone",
basePath: process.env.NODE_ENV === "production" ? basePath : "",
basePath: basePath,
env: {
BASE_PATH: basePath,
},
Expand Down
8 changes: 3 additions & 5 deletions containers/ecr-viewer/src/app/components/EcrTableClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { toSentenceCase } from "@/app/services/formatService";
import { usePathname, useSearchParams, useRouter } from "next/navigation";
import { range } from "../view-data/utils/utils";
import classNames from "classnames";

const basePath =
process.env.NODE_ENV === "production" ? process.env.BASE_PATH : "";
import Link from "next/link";

type EcrTableClientProps = {
data: EcrDisplay[];
Expand Down Expand Up @@ -304,9 +302,9 @@ const DataRow = ({ item }: { item: EcrDisplay }) => {
return (
<tr>
<td>
<a href={`${basePath}/view-data?id=${item.ecrId}`}>
<Link href={`/view-data?id=${item.ecrId}`}>
{patient_first_name} {patient_last_name}
</a>
</Link>
<br />
<div>{"DOB: " + item.patient_date_of_birth || ""}</div>
</td>
Expand Down
5 changes: 1 addition & 4 deletions containers/ecr-viewer/src/app/components/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import React, { useCallback, useEffect, useState } from "react";
import { Button, Icon } from "@trussworks/react-uswds";
import { useRouter, usePathname, useSearchParams } from "next/navigation";

const basePath =
process.env.NODE_ENV === "production" ? process.env.BASE_PATH : "";

/**
* Functional component that renders Filters section in eCR Library.
* Includes Filter component for reportable conditions.
Expand Down Expand Up @@ -45,7 +42,7 @@ const FilterReportableConditions = () => {
useEffect(() => {
const fetchConditions = async () => {
try {
const response = await fetch(`${basePath}/api/conditions`);
const response = await fetch(`${process.env.BASE_PATH}/api/conditions`);
if (!response.ok) {
throw new Error("Failed to fetch conditions");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ describe("Filters Component", () => {
const { container } = render(<Filters />);

await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith("/api/conditions");
expect(global.fetch).toHaveBeenCalledWith(
`${process.env.BASE_PATH}/api/conditions`,
);
});
const toggleFilterButton = screen.getByRole("button", {
name: /Filter by reportable condition/i,
Expand Down Expand Up @@ -69,7 +71,9 @@ describe("Filters Component", () => {

// Should have called conditions endpoint
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith("/api/conditions");
expect(global.fetch).toHaveBeenCalledWith(
`${process.env.BASE_PATH}/api/conditions`,
);
});

fireEvent.click(toggleButton);
Expand Down
8 changes: 7 additions & 1 deletion containers/ecr-viewer/src/app/tests/view-data.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ function mockFetch(data: any, status?: number, statusText?: string) {
}

describe("ECRViewerPage", () => {
beforeAll(() => {
process.env.BASE_PATH = "ecr-viewer";
});
afterAll(() => {
delete process.env.BASE_PATH;
});
it("calls metrics on beforeunload", () => {
window.fetch = mockFetch({});

Expand All @@ -37,7 +43,7 @@ describe("ECRViewerPage", () => {

// Verify that the metrics function was called

expect(metricsMock).toHaveBeenCalledWith("", {
expect(metricsMock).toHaveBeenCalledWith("ecr-viewer", {
fhirId: "",
startTime: expect.any(Number),
endTime: expect.any(Number),
Expand Down
2 changes: 1 addition & 1 deletion containers/ecr-viewer/src/app/view-data/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const ECRViewerPage: React.FC = () => {
useEffect(() => {
const startTime = performance.now();
window.addEventListener("beforeunload", function (_e) {
metrics("", {
metrics(process.env.BASE_PATH, {
startTime: startTime,
endTime: performance.now(),
fhirId: `${fhirId}`,
Expand Down
2 changes: 2 additions & 0 deletions containers/ecr-viewer/src/app/view-data/retrieval-failed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from "react";
import { Icon } from "@trussworks/react-uswds";
import Header from "../Header";
import { BackButton } from "./components/BackButton";

/**
* @returns The ecr retrieval error page JSX component.
Expand Down Expand Up @@ -31,6 +32,7 @@ const RetrievalFailed = () => (
<br /> to troubleshoot the issue with the DIBBs team.
</div>
</div>
<BackButton className="margin-top-3 font-sans-md text-primary" />
</div>
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ describe("Middleware", () => {
beforeEach(() => {
process.env.NEXTAUTH_SECRET = "test-secret";
process.env.APP_ENV = "middleware";
process.env.BASE_PATH = "ecr-viewer";
jest.resetAllMocks(); // Reset mocks before each test
});
afterEach(() => {
delete process.env.NEXTAUTH_SECRET;
delete process.env.APP_ENV;
delete process.env.BASE_PATH;
});

it("should authorize the request if oauthToken is present", async () => {
getToken.mockImplementation(() => Promise.resolve({ token: "fake-token" }));

const req = new NextRequest("https://www.example.com/api/protected");
const req = new NextRequest(
"https://www.example.com/ecr-viewer/api/protected",
);

const resp = await middleware(req);

Expand All @@ -39,7 +47,7 @@ describe("Middleware", () => {

it("should strip the auth query param and set the token", async () => {
const req = new NextRequest(
"https://www.example.com/api?id=1234&auth=abcd",
"https://www.example.com/ecr-viewer/api?id=1234&auth=abcd",
);

const resp = await middleware(req);
Expand All @@ -50,24 +58,28 @@ describe("Middleware", () => {
httpOnly: true,
});
expect(resp.headers.get("location")).toBe(
"https://www.example.com/api?id=1234",
"https://www.example.com/ecr-viewer/api?id=1234",
);
});

it("should not authorize the api endpoints without auth", async () => {
const req = new NextRequest("https://www.example.com/api/fhir-data/");
const req = new NextRequest(
"https://www.example.com/ecr-viewer/api/fhir-data/",
);

const resp = await middleware(req);
expect(resp.headers.get("location")).toBe(
"https://www.example.com/error/auth",
expect(resp.headers.get("x-middleware-rewrite")).toBe(
"https://www.example.com/ecr-viewer/error/auth",
);
expect(resp.status).toBe(307);
expect(resp.status).toBe(200);
});

it("should authorize the api endpoints with auth", async () => {
process.env.NBS_PUB_KEY = "FOOBAR";

const req = new NextRequest("https://www.example.com/api/fhir-data/");
const req = new NextRequest(
"https://www.example.com/ecr-viewer/api/fhir-data/",
);
req.cookies.set("auth-token", "foobar");

const resp = await middleware(req);
Expand All @@ -78,11 +90,13 @@ describe("Middleware", () => {
});

it("should not authorize non api endpoints ", async () => {
const req = new NextRequest("https://www.example.com/view-data?id=1234");
const req = new NextRequest(
"https://www.example.com/ecr-viewer/view-data?id=1234",
);
const resp = await middleware(req);
expect(resp.headers.get("location")).toBe(
"https://www.example.com/error/auth",
expect(resp.headers.get("x-middleware-rewrite")).toBe(
"https://www.example.com/ecr-viewer/error/auth",
);
expect(resp.status).toBe(307);
expect(resp.status).toBe(200);
});
});
24 changes: 22 additions & 2 deletions containers/ecr-viewer/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export async function middleware(req: NextRequest): Promise<NextResponse> {
if (nbsAuth) {
return nbsAuth;
} else {
return NextResponse.redirect(new URL("/error/auth", req.url));
return NextResponse.rewrite(
new URL(`${process.env.BASE_PATH}/error/auth`, req.nextUrl.origin),
);
}
}
} else {
Expand All @@ -35,7 +37,25 @@ export async function middleware(req: NextRequest): Promise<NextResponse> {
}

export const config = {
matcher: ["/api/fhir-data", "/api/metrics", "/view-data", "/"],
matcher: [
/*
* Run middleware on all request paths except these:
* - Api routes
* - _next/static (static files)
* - _next/image (image optimization files)
* - images (static files in public/images/ directory)
*/
"/((?!api|_next/static|_next/image|public|img|uswds|images).*)",
/**
* Fix issue where the pattern above was causing middleware
* to not run on the homepage:
*/
"/",
/**
* Run middleware on getting fhir-data
*/
"/api/fhir-data",
],
};

/**
Expand Down

0 comments on commit 77db6a9

Please sign in to comment.