From de5ef553ce40dd1a9aa6ff47d65c631f7d62666b Mon Sep 17 00:00:00 2001 From: "Adam J. Arling" Date: Wed, 15 Mar 2023 21:01:28 +0000 Subject: [PATCH] Only include Reading Room message on restricted Works. Update README --- README.md | 128 ++++++++++---------- components/Facets/Filter/GroupList.test.tsx | 4 +- components/Work/ViewerWrapper.test.tsx | 28 +++-- components/Work/ViewerWrapper.tsx | 5 +- lib/dc-api.ts | 1 + pages/collections/[id].tsx | 12 +- pages/items/[id].tsx | 22 ++-- 7 files changed, 111 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 3b66c4ab..31d9fa54 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ -# Digital Collections v2 NextJS App +# Digital Collections v2 -This is a work in progress, for experimenting with NextJS, AWS Amplify and various ways of building / data fetching / hosting. +Digital Collections v2 (DCv2) is a UI application for discovering and interacting with Collections and Works in NUL's repository. -## Getting Started +## Tech Stack +- [NextJS](https://nextjs.org/) React JS fullstack framework +- [TypeScript](https://www.typescriptlang.org/) for type safety +- [Radix UI](https://www.radix-ui.com/) A library of React primitives for accessibility and modular development +- [Stitches.dev](https://stitches.dev/) CSS in JS +- [IIIF](https://iiif.io/) Research APIs and Specs our data conforms to for open access. +- [AWS Amplify](https://aws.amazon.com/amplify/) Hosting environment +- [OpenSearch](https://opensearch.org/) Search index + +## Development Environments +### Local Install dependencies and run a NextJS development server: ```bash @@ -11,25 +21,69 @@ npm install npm run dev ``` -Open [http://localhost:3000](http://localhost:3000) with your browser. +Open [http://devbox.library.northwestern.edu:3000](http://devbox.library.northwestern.edu:3000) in your browser. -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. +### AWS Developer Environment (Northwestern dev team only) +Open a remote SSH dev environment connection in VSCode. -## Deploying +`cd` into the `dc-nextjs` repository -### NextJS Environment +1. Open a new terminal. -Commits to the `deploy/staging` branch will trigger a build in an AWS Amplify Hosting solution. +2. Make sure port 3000 is open by running `sg show`. If you don't see port 3000, run `sg open all 3000`. View more in [AWS convenience scripts](https://github.com/nulib/aws-developer-environment#convenience-scripts). -Commits prefaced with `preview/branch-name-here` will deploy to a preview branch +3. Temporarily change the following line in (`dc-nextjs/server.js`): +```js -### Data fetching +// Change +const hostname = "devbox.library.northwestern.edu"; +// ...to +const hostname = "localhost"; +``` +Install dependencies -Currently in the Amplify AWS environment (Dec 2022), note that SSR (Server Side Rendering), will not pass authentication JWT tokens properly. +```bash +npm install +npm run dev +``` -The primary dynamic route pages (`items/[id]` and `collections/[id]`), will support [Incremental Static Regeneration](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration), but we'll keep build time routes to statically generate at 1 for both a Work and Collection. +And now open your AWS dev environment URL (Northwestern developers only). -```` + +## Deploy Environments +### Staging + +Commits (via merges) into the `deploy/staging` branch will trigger a build in AWS Amplify to the **staging** environment. + +https://dc.rdc-staging.library.northwestern.edu/ + +Commits prefaced with `preview/branch-name-here` will deploy to a preview branch. The URL will be available within AWS Amplify. This is useful for sharing the feature with staff/users as a preview before committing to staging. + +### Production + +Commits (via merges) into the `main` branch will trigger a build in AWS Amplify to the **production** environment. + +https://dc.library.northwestern.edu/ + +## Data fetching / APIs + +The application makes network requests against the [DC API v2](https://github.com/nulib/dc-api-v2) to access repository data. By default, all metadata is returned in the application. Authenticated content's media (image/audio/video) will be protected and obscured from public access. + +Behind the scenes, DC API v2 is using OpenSearch `v 1.2` or Elasticsearch `v 7.17`. (For documentation references). Network request urls with `?as=iiif` will return data in the shape of a [IIIF](https://iiif.io/) manifest. + +### Viewing the Index (OpenSearch) directly +OpenSearch's data can be accessed directly via [Kibana](https://www.elastic.co/kibana/) by executing the following commands: + +```bash +export AWS_PROFILE=staging +aws-adfs login --profile $AWS_PROFILE +es-proxy +``` + +The API supports both POST for searching and GET for Work and Collection items. + +### Environment variables +The API endpoint is an environment variable which is accessed in a local dev environment via the `miscellany` Git repo. ## Code Quality @@ -80,54 +134,6 @@ To run [Jest](https://jestjs.io/) w/ [React Testing-Library](https://testing-lib npm run test ``` -## API - -### Notes - -Currently DC v2 hits a new DC API v2 for it's indexed data. - -`https://dcapi.rdc.library.northwestern.edu/docs/v2` - -Behind the scenes, DC API v2 is using OpenSearch `v 1.2` or Elasticsearch `v 7.17`. (For documentation references). - -### Endpoints - -The API endpoint is an environment variable which is accessed in a local dev environment via the `miscellany` Git repo. The dev environment runs against Staging. - -### Viewing OpenSearch data locally - -In a local dev environment, to view API data coming from the index (for now), run the following: - -``` -export AWS_PROFILE=staging -aws-adfs login --profile $AWS_PROFILE -es-proxy -``` - -The API supports both POST for searching and GET for Work and Collection items. - -### Search examples - -``` -# Search Works (default) -curl -X POST '[URL]/search' --data-binary '{"query": {"match_all": {}}, "_source": "id", "size": 1000}' | jq - -# Search Collections -curl -X POST '[URL]/search/collections' --data-binary '{"query": {"match_all": {}}, "_source": "id", "size": 1000}' | jq -``` - -### Direct GET request endpoint examples - -``` -[URL]/works/4359936f-9091-499b-893f-b8e900db49ec - -[URL]/collections/18ec4c6b-192a-4ab8-9903-ea0f393c35f7 - -[URL]/file-sets/ce1f6d18-8563-4f70-aabc-d4ce1688d8dc -``` - -See documentation in above link for more info - ## Optimizations `npm run analyze` will run the [Next Bundle Analyzer](https://github.com/vercel/next.js/tree/canary/packages/next-bundle-analyzer) to show snapshots of the app's bundled JS. diff --git a/components/Facets/Filter/GroupList.test.tsx b/components/Facets/Filter/GroupList.test.tsx index cd2cc02d..1014efd3 100644 --- a/components/Facets/Filter/GroupList.test.tsx +++ b/components/Facets/Filter/GroupList.test.tsx @@ -81,10 +81,10 @@ describe("FacetsGroupList component", () => { * Looks like Radix puts this active state data attribute * on the element.... good for testing against:) */ - const tabActive = await screen.findAllByRole("tab", { exact: true }); + const tabActive = await screen.findAllByRole("tab"); expect(tabActive[0].dataset.state).toEqual("active"); - const tabInactive = await screen.findAllByRole("tab", { exact: true }); + const tabInactive = await screen.findAllByRole("tab"); expect(tabInactive[1].dataset.state).toEqual("inactive"); }); diff --git a/components/Work/ViewerWrapper.test.tsx b/components/Work/ViewerWrapper.test.tsx index 4c7eb53f..8c3f6370 100644 --- a/components/Work/ViewerWrapper.test.tsx +++ b/components/Work/ViewerWrapper.test.tsx @@ -23,28 +23,36 @@ describe("WorkViewerWrapper", () => { }); }); - it("renders an announcement when in the Reading Room", async () => { + it("renders an announcement when in the Reading Room only when the Work is protected", async () => { + const readingUserContext = { ...userContextValue }; + readingUserContext.user.isReadingRoom = true; + render( - - + + ); - expect(screen.queryByText(readingRoomMessage)).not.toBeInTheDocument(); + expect( + await screen.findByText(readingRoomMessage) + ).toBeInTheDocument(); + }); + + it("does not render an announcement when in the Reading Room and the Work is not restricted", async () => { const readingUserContext = { ...userContextValue }; readingUserContext.user.isReadingRoom = true; render( - + ); - expect( - await screen.findByText( - /You have access to Work because you are in the reading room/i - ) - ).toBeInTheDocument(); + let el; + await waitFor(() => { + el = screen.queryByText(readingRoomMessage); + }) + expect(el).toBeNull(); }); }); diff --git a/components/Work/ViewerWrapper.tsx b/components/Work/ViewerWrapper.tsx index b1d24b32..ebdbe4b4 100644 --- a/components/Work/ViewerWrapper.tsx +++ b/components/Work/ViewerWrapper.tsx @@ -25,9 +25,10 @@ export const CloverIIIF: React.ComponentType<{ interface WrapperProps { manifestId: Work["iiif_manifest"]; + isWorkRestricted?: boolean; } -const WorkViewerWrapper: React.FC = ({ manifestId }) => { +const WorkViewerWrapper: React.FC = ({ manifestId, isWorkRestricted }) => { const userAuth = React.useContext(UserContext); const customTheme = { @@ -67,7 +68,7 @@ const WorkViewerWrapper: React.FC = ({ manifestId }) => { options={options} /> )} - {userAuth?.user?.isReadingRoom && ( + {isWorkRestricted && userAuth?.user?.isReadingRoom && ( diff --git a/lib/dc-api.ts b/lib/dc-api.ts index 48c0b8c4..12f37014 100644 --- a/lib/dc-api.ts +++ b/lib/dc-api.ts @@ -14,6 +14,7 @@ async function apiGetRequest( obj: ApiGetRequestParams ): Promise { const { url } = obj; + try { const response = await axios({ url, diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index d327ff5c..cec170f9 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -59,10 +59,15 @@ const Collection: NextPage = () => { const id = router.query.id; if (!id || Array.isArray(id)) return; const data = await getCollection(id); + + // This is not preferred, but auth is only respected client side + // so need this for items to display in Reading Room + if (!data) return router.push("/404"); + setCollection(data); } router.isReady && getData(); - }, [router.isReady, router.query.id]); + }, [router, router.isReady, router.query.id]); /** Get dependant data */ useEffect(() => { @@ -203,11 +208,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const id = context?.params?.id; const collection = await getCollection(id as string); - if (typeof collection === "undefined") - return { - notFound: true, - }; - /** Add values to GTM's dataLayer object */ const dataLayer = buildDataLayer({ adminset: "", diff --git a/pages/items/[id].tsx b/pages/items/[id].tsx index 1497e57e..e1042178 100644 --- a/pages/items/[id].tsx +++ b/pages/items/[id].tsx @@ -22,6 +22,7 @@ import { buildWorkDataLayer } from "@/lib/ga/data-layer"; import { buildWorkOpenGraphData } from "@/lib/open-graph"; import { getIIIFResource } from "@/lib/dc-api"; import { loadItemStructuredData } from "@/lib/json-ld"; +import { useRouter } from "next/router"; import useWorkAuth from "@/hooks/useWorkAuth"; interface WorkPageProps { @@ -35,6 +36,7 @@ const WorkPage: NextPage = ({ collectionWorkCounts, id }) => { const [work, setWork] = useState(); const [manifest, setManifest] = useState(); const { isWorkRestricted } = useWorkAuth(work); + const router = useRouter(); const isReadingRoom = userAuthContext?.user?.isReadingRoom; const related = work ? getWorkSliders(work) : []; @@ -48,7 +50,13 @@ const WorkPage: NextPage = ({ collectionWorkCounts, id }) => { async function getData() { const work = await getWork(id); - if (!work) return setIsLoading(false); + if (!work) { + // This is not preferred, but auth is only respected client side + // so need this for items to display in Reading Room + router.push("/404"); + + return setIsLoading(false); + } setWork(work); const manifest = await getIIIFResource(work.iiif_manifest); setManifest(manifest); @@ -56,7 +64,7 @@ const WorkPage: NextPage = ({ collectionWorkCounts, id }) => { } getData(); - }, [id]); + }, [id, router]); return ( <> @@ -83,7 +91,10 @@ const WorkPage: NextPage = ({ collectionWorkCounts, id }) => { {work.iiif_manifest && (isReadingRoom || !isWorkRestricted) && ( - + )} {work && !isReadingRoom && isWorkRestricted && ( @@ -114,11 +125,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const id = context?.params?.id; const work = await getWork(id as string); - if (typeof work === "undefined") - return { - notFound: true, - }; - const collectionWorkCounts = work?.collection ? await getCollectionWorkCounts(work?.collection.id) : null;