Skip to content

Commit

Permalink
Use session storage to persist filter and search (#3114)
Browse files Browse the repository at this point in the history
* saving urlParams to session storage, testing retrieval and setting

* save to session storage on name click, apply to back button

* add tests for new session storage utils

* typo fix

* Add playwright test, fix session storage undefined error

* Add undefined return type and return undefined if session storage doesnt exist to fix error

* change undefined return to null

* update description

* mock session storage for playwright test

* update to use locator method

* nevermind on the e2e for sessionStorage
  • Loading branch information
gordonfarrell authored Jan 13, 2025
1 parent 2055a30 commit 7c75a98
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 5 deletions.
10 changes: 8 additions & 2 deletions containers/ecr-viewer/src/app/components/EcrTableClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { usePathname, useSearchParams, useRouter } from "next/navigation";
import { noData, range } from "../view-data/utils/utils";
import classNames from "classnames";
import Link from "next/link";
import { saveToSessionStorage } from "./utils";

type EcrTableClientProps = {
data: EcrDisplay[];
Expand Down Expand Up @@ -66,7 +67,7 @@ const initialHeaders = [
{
id: "reportable_condition",
value: "Reportable Condition",
className: "library-condition-colum",
className: "library-condition-column",
dataSortable: false,
sortDirection: "",
},
Expand Down Expand Up @@ -277,6 +278,8 @@ const DataRow = ({ item }: { item: EcrDisplay }) => {
const patient_first_name = toSentenceCase(item.patient_first_name);
const patient_last_name = toSentenceCase(item.patient_last_name);

const searchParams = useSearchParams();

const conditionsList = (
<ul className="ecr-table-list">
{item.reportable_conditions.map((rc, index) => (
Expand All @@ -292,11 +295,14 @@ const DataRow = ({ item }: { item: EcrDisplay }) => {
))}
</ul>
);
const saveUrl = () => {
saveToSessionStorage("urlParams", searchParams.toString());
};

return (
<tr>
<td>
<Link href={`/view-data?id=${item.ecrId}`}>
<Link onClick={saveUrl} href={`/view-data?id=${item.ecrId}`}>
{patient_first_name} {patient_last_name}
</Link>
<br />
Expand Down
34 changes: 34 additions & 0 deletions containers/ecr-viewer/src/app/components/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Saves a key-value pair to session storage.
* @param key - The key under which the value will be stored.
* @param value - The value to be stored. Can be a string or any serializable object.
*/
export const saveToSessionStorage = (
key: string,
value: string | object,
): void => {
const serializedValue = JSON.stringify(value);

if (typeof window !== "undefined" && window.sessionStorage) {
sessionStorage.setItem(key, serializedValue);
} else {
console.warn("sessionStorage is not available");
}
};

/**
* Retrieves a key-value pair from session storage.
* @param key - The key under which the value was stored.
* @returns string or null - The stored value from session storage or null if it finds nothing
*/
export const retrieveFromSessionStorage = (
key: string,
): string | object | null => {
if (typeof window !== "undefined" && window.sessionStorage) {
const storedValue = sessionStorage.getItem(key);
return JSON.parse(<string>storedValue);
} else {
console.warn("sessionStorage is not available");
return null;
}
};
62 changes: 62 additions & 0 deletions containers/ecr-viewer/src/app/tests/components/Utils.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
saveToSessionStorage,
retrieveFromSessionStorage,
} from "../../components/utils.ts";

describe("Session Storage saving utils", () => {
beforeEach(() => {
sessionStorage.clear();
});

describe("saveToSessionStorage", () => {
it("should save a string value to session storage", () => {
const key = "Bread";
const value = "Baguette";
saveToSessionStorage(key, value);

expect(sessionStorage.getItem(key)).toBe(JSON.stringify(value));
});

it("should save an object value to session storage", () => {
const key = "testKey";
const value = { name: "Heironymous", age: 37 };
saveToSessionStorage(key, value);

expect(sessionStorage.getItem(key)).toBe(JSON.stringify(value));
});
});

describe("retrieveFromSessionStorage", () => {
it("should retrieve a string value from session storage", () => {
const key = "fruit";
const value = "Apples";
sessionStorage.setItem(key, JSON.stringify(value));

const retrievedValue = retrieveFromSessionStorage(key);
expect(retrievedValue).toBe(value);
});

it("should retrieve an object value from session storage", () => {
const key = "patient";
const value = { name: "Arabelle", age: 22 };
sessionStorage.setItem(key, JSON.stringify(value));

const retrievedValue = retrieveFromSessionStorage(key);
expect(retrievedValue).toEqual(value);
});

it("should return null if the key does not exist", () => {
const key = "nonExistentKey";
const retrievedValue = retrieveFromSessionStorage(key);

expect(retrievedValue).toBeNull();
});

it("should throw an error for invalid JSON in session storage", () => {
const key = "aKey";
sessionStorage.setItem(key, "invalid-json");

expect(() => retrieveFromSessionStorage(key)).toThrow(SyntaxError);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ exports[`EcrTable Snapshot test for EcrTableLoading should match snapshot 1`] =
</div>
</th>
<th
class="library-condition-colum"
class="library-condition-column"
data-sortable="false"
id="reportable_condition-header"
role="columnheader"
Expand Down Expand Up @@ -953,7 +953,7 @@ exports[`EcrTable should match snapshot 1`] = `
</div>
</th>
<th
class="library-condition-colum"
class="library-condition-column"
data-sortable="false"
id="reportable_condition-header"
role="columnheader"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Icon } from "@trussworks/react-uswds";
import Link from "next/link";
import classNames from "classnames";
import { env } from "next-runtime-env";
import { retrieveFromSessionStorage } from "../../components/utils";

interface BackButtonProps {
className?: string;
Expand All @@ -19,11 +20,14 @@ interface BackButtonProps {
export const BackButton = ({ className, iconClassName }: BackButtonProps) => {
const isNonIntegratedViewer =
env("NEXT_PUBLIC_NON_INTEGRATED_VIEWER") === "true";

const savedUrlParams = retrieveFromSessionStorage("urlParams");

return (
<>
{isNonIntegratedViewer ? (
<Link
href={"/"}
href={savedUrlParams ? `/?${savedUrlParams}` : "/"}
className={classNames("display-inline-block", className)}
>
<Icon.ArrowBack
Expand Down

0 comments on commit 7c75a98

Please sign in to comment.