Skip to content

Commit

Permalink
Merge pull request #218 from NYPL/release/0.1.16
Browse files Browse the repository at this point in the history
Release/0.1.16
  • Loading branch information
7emansell authored Oct 31, 2024
2 parents 39e210c + d63a18d commit e8f414f
Show file tree
Hide file tree
Showing 40 changed files with 1,403 additions and 1,363 deletions.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ API_CACHE=false # Optional, controls whether or not api responses are cached
DC_URL='' # OR https://qa-digitalcollections.nypl.org OR https://digitalcollections.nypl.org

# Feedback form
CLIENT_ID= # Available in parameter store
CLIENT_SECRET= # Available in parameter store
CLIENT_EMAIL= # Available in parameter store

GOOGLE_SHEETS_PRIVATE_KEY=[YOUR KEY] # Available in parameter store
GOOGLE_SHEETS_CLIENT_EMAIL=[YOUR ACCOUNT EMAIL] # Available in parameter store
SPREADSHEET_ID=[YOU CAN GET THIS ON URL OF YOUR SHEETS] # Available in parameter store

# New Relic
NEW_RELIC_LICENSE_KEY=[NEWRELIC LICENSE KEY] # Available in parameter store
NEW_RELIC_APP_NAME="Facelift QA"

# Adobe Analytics
ADOBE_EMBED_URL=https://assets.adobedtm.com/1a9376472d37/8519dfce636d/launch-bf8436264b01-development.min.js
APP_ENV=development
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Updated

- Updated cards to use Next Image (DR-3056)

## [0.1.16] 2024-10-31

### Updated

- Updated links in QA to point internally for reverse proxy (DR-3237)
- Updated how env vars are read for New Relic Browser implementation (DR-3235)
- Refactored collections/items grid into one `CardsGrid` component (DR-3193)
- Updated 500 and 404 error page designs, adding link to open feedback box (DR-3203)

## [0.1.15] 2024-10-10

### Updated

- Refactored collection/item lanes into one `Lane` component (DR-3191)
Expand Down
8 changes: 7 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ COPY . .

ENV APP_ENV=${APP_ENV}
ENV NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}
ENV NEW_RELIC_APP_NAME="Facelift ${APP_ENV}"
ENV API_URL=${API_URL}
ENV AUTH_TOKEN=${AUTH_TOKEN}

# Create a `.env.prod` file with the environment variables
# This is a workaround to use environment variables in the `newrelic.js` file
RUN echo "NEW_RELIC_LICENSE_KEY=${NEW_RELIC_LICENSE_KEY}" >> /etc/.env.prod
RUN echo "NEW_RELIC_APP_NAME=${NEW_RELIC_APP_NAME}" >> /etc/.env.prod

# Set environment variables. NODE_ENV is set early because we
# want to use it when running `npm install` and `npm run build`.
ENV PATH /app/node_modules/.bin:$PATH
Expand All @@ -35,4 +41,4 @@ RUN npm run build
EXPOSE 3000

# CMD is the default command when running the docker container.
CMD [ "npm", "start" ]
CMD [ "npm", "run", "start:newrelic" ]
12 changes: 12 additions & 0 deletions ENVIRONMENTVARS.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ These environment variables control how certain elements on the page render and
| `GOOGLE_SHEETS_PRIVATE_KEY` | string | "" | |
| `SPREADSHEET_ID` | string | "" | |
| `NEW_RELIC_LICENSE_KEY` | string | "true" | |

## New Relic Variables

The following variables are needed in the global env var scope in order for New
Relic to pick up the values, specifically in the `newrelic.js` file.

These env vars are not picked up by default in Next.js' `.env.local` file. In order for this to work, the `dotenv` package is used to declare these env vars when running either `npm run dev:newrelic` or `npm run start:newrelic`.

| Variable | Type | Value Example | Description |
| ----------------------- | ------ | -------------- | -------------------------------------------------- |
| `NEW_RELIC_LICENSE_KEY` | string | "true" | Private license key. Available in parameter store. |
| `NEW_RELIC_APP_NAME` | string | "Facelift QA" | |
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ You can start editing the page by modifying `pages/index.tsx`. The page auto-upd

The `app/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

### New Relic Start

If New Relic needs to run locally, run `npm run dev:newrelic`. You must have `NEW_NEW_RELIC_LICENSE_KEY` and `NEW_RELIC_APP_NAME` declared in `.env.local` in order for this to run successfully.

## Environment Variables

A quick note on environment variables
Expand Down
47 changes: 47 additions & 0 deletions __tests__/pages/errorpage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import PageLayout from "@/src/components/pageLayout/pageLayout";
import ErrorPage from "@/src/components/pages/errorPage/errorPage";
import { fireEvent, render, screen } from "@testing-library/react";
import { useRouter } from "next/navigation";
import React from "react";

jest.mock("next/navigation", () => ({
useRouter: jest.fn(),
}));

describe("Error Page", () => {
it("renders an error message", () => {
(useRouter as jest.Mock).mockImplementation(() => ({
pathname: "/test-error",
}));

render(
<PageLayout activePage={"serverError"}>
<ErrorPage />
</PageLayout>
);

const heading = screen.getByRole("heading", {
name: /Something went wrong on our end./i,
});
expect(heading).toBeInTheDocument();
});

it("has a link that can open the feedback box", async () => {
(useRouter as jest.Mock).mockImplementation(() => ({
pathname: "/test-error",
}));

render(
<PageLayout activePage={"serverError"}>
<ErrorPage />
</PageLayout>
);

const button = screen.getByText(/contact us/);
fireEvent.click(button);

expect(
screen.getByText(/What is your feedback about?/)
).toBeInTheDocument();
});
});
4 changes: 3 additions & 1 deletion __tests__/pages/notfoundpage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ describe("Not Found Page", () => {
}));

render(<NotFoundPage />);
const heading = screen.getByRole("heading", { name: /404/i });
const heading = screen.getByRole("heading", {
name: /We couldn't find that page./i,
});
expect(heading).toBeInTheDocument();
});
});
30 changes: 10 additions & 20 deletions app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
"use client";
import { Box, Heading } from "@nypl/design-system-react-components";
import Link from "next/link";
import PageLayout from "./src/components/pageLayout/pageLayout";
import { useEffect } from "react";

import PageLayout from "./src/components/pageLayout/pageLayout";
import ErrorPage from "./src/components/pages/errorPage/errorPage";

export default function Error({
error,
}: {
error: Error & { digest?: string };
}) {
useEffect(() => {
console.error(error);

// Send error to New Relic
if ((window as any).newrelic) {
(window as any).newrelic.noticeError(error);
}
}, [error]);

return (
<PageLayout activePage="serverError">
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "xl",
textAlign: "center",
}}
>
<Heading level="h1">Error</Heading>
<p>We&apos;re sorry...</p>
<p>Something went wrong.</p>
<p>
Return to <Link href={"/"}>Digital Collections</Link>.
</p>
</Box>
<ErrorPage />
</PageLayout>
);
}
28 changes: 25 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Script from "next/script";
import React from "react";
import newrelic from "newrelic";
import { Metadata } from "next";
import "./globals.css";
import { headers } from "next/headers";
import Script from "next/script";

import "./globals.css";

export const metadata: Metadata = {
title: "NYPL Digital Collections",
Expand Down Expand Up @@ -57,11 +59,24 @@ export async function generateViewport() {
: {};
}

export default function RootLayout({
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
// Not ideal but `any` for now.
const newRelicTyped: any = newrelic;
// Track data for New Relic Browser
if (newRelicTyped?.agent?.collector?.isConnected() === false) {
await new Promise((resolve) => {
newRelicTyped?.agent?.on("connected", resolve);
});
}
const browserTimingHeader = newRelicTyped?.getBrowserTimingHeader({
hasToRemoveScriptWrapper: true,
allowTransactionlessInjection: true,
});

return (
<html lang="en">
<head>
Expand All @@ -84,6 +99,13 @@ export default function RootLayout({
src="https://ds-header.nypl.org/footer.min.js?containerId=nypl-footer"
async
></Script>
<Script
id="nr-browser-agent"
// By setting the strategy to "beforeInteractive" we guarantee that
// the script will be added to the document's `head` element.
strategy="beforeInteractive"
dangerouslySetInnerHTML={{ __html: browserTimingHeader }}
/>
</body>
</html>
);
Expand Down
3 changes: 3 additions & 0 deletions app/src/components/card/card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { mockItems } from "__tests__/__mocks__/data/mockItems";
describe("Collection DCCard component", () => {
const mockCollectionProps = {
cardOffset: [0, -130],
imageHeight: 144,
slug: "test-slug",
id: "1",
isLargerThanLargeTablet: true,
Expand All @@ -15,6 +16,7 @@ describe("Collection DCCard component", () => {

const mockCollectionPropsNoOnSite = {
cardOffset: [0, -130],
imageHeight: 144,
slug: "test-slug",
id: "1",
isLargerThanLargeTablet: true,
Expand Down Expand Up @@ -60,6 +62,7 @@ describe("Collection DCCard component", () => {
describe("Item DCCard component", () => {
const mockItemProps = {
cardOffset: [0, -130],
imageHeight: 144,
id: "1",
isLargerThanLargeTablet: true,
record: new ItemCardModel(mockItems[0]),
Expand Down
19 changes: 11 additions & 8 deletions app/src/components/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import {
Tooltip,
StatusBadge,
} from "@nypl/design-system-react-components";
import styles from "./card.module.css";
import { headerBreakpoints } from "../../utils/breakpoints";
import { TRUNCATED_LENGTH } from "@/src/config/constants";
import ItemCardDataType from "@/src/types/ItemCardDataType";
import { CollectionCardDataType } from "../../types/CollectionCardDataType";
import { Offset } from "@/src/hooks/useTooltipOffset";
interface DCCardProps {
import { stringToSlug } from "@/src/utils/utils";
export interface DCCardProps {
tooltipOffset?: Offset;
id: string;
isLargerThanLargeTablet: boolean;
slug?: string;
record: CollectionCardDataType | ItemCardDataType;
}

function isCollectionCardDataType(
export function isCollectionCardDataType(
record: CollectionCardDataType | ItemCardDataType
): record is CollectionCardDataType {
return "numberOfDigitizedItems" in record;
Expand All @@ -32,16 +32,19 @@ export const Card = forwardRef<HTMLDivElement, DCCardProps>(
({ tooltipOffset, id, isLargerThanLargeTablet, slug, record }, ref) => {
const truncatedTitle = record.title.length > TRUNCATED_LENGTH;
const isCollection = isCollectionCardDataType(record);
const identifier = slug
? `${slug}-${id}`
: `${stringToSlug(record.title)}-${id}`; // should probably truncate
const card = (
<ChakraCard
ref={ref}
id={`card-${slug}-${id}`}
id={`card-${identifier}`}
mainActionLink={record.url}
imageProps={
record.imageID
? {
alt: "",
id: isCollection ? `image-${slug}-${id}` : `image-${id}`,
id: `image-${identifier}`,
isLazy: true,
aspectRatio: "twoByOne",
fallbackSrc: "/noImage.png",
Expand All @@ -53,7 +56,7 @@ export const Card = forwardRef<HTMLDivElement, DCCardProps>(
}
: {
alt: "",
id: `no-image-${id}`,
id: `no-image-${identifier}`,
isLazy: true,
aspectRatio: "twoByOne",
src: "/noImage.png",
Expand All @@ -68,7 +71,7 @@ export const Card = forwardRef<HTMLDivElement, DCCardProps>(
)}
</CardContent>
<CardHeading
id={`row-card-heading-${slug}-${id}`}
id={`row-card-heading-${identifier}`}
level="h3"
size="heading5"
noOfLines={3}
Expand All @@ -88,7 +91,7 @@ export const Card = forwardRef<HTMLDivElement, DCCardProps>(
<CardContent sx={{ alignContent: "top" }}>
{isCollection && (
<Text
id={`item-count-${slug}-${id}`}
id={`item-count-${identifier}`}
size="subtitle2"
fontWeight="medium"
__css={{
Expand Down
49 changes: 49 additions & 0 deletions app/src/components/card/cardImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Image from "next/image";
import React from "react";
import { useState } from "react";
import { DCCardProps } from "./card";

interface CardImageProps extends Pick<DCCardProps, "record"> {
imageHeight: number;
}

export const CardImage = ({ record, imageHeight }: CardImageProps) => {
const [imageSrc, setImageSrc] = useState(
record.imageID ? record.imageURL : "/noImage.png"
);
const initialImageHeight = 144;
return (
<div
style={{
overflow: "hidden",
height: imageHeight,
}}
>
<Image
src={imageSrc}
alt=""
id={
record.imageID
? `image-${record.imageID}`
: `no-image-${record.imageID}`
}
sizes="(max-width: 480px) 100vw, (max-width: 1024px) 50vw, 25vw"
style={{
width: "100%",
minHeight: "100%",
height: "auto",
}}
width={initialImageHeight * 2}
height={initialImageHeight}
onError={(_event) => {
console.warn(
`CardImage: Card image failed to load, fallback image loaded instead. ImageURL: ${record.imageURL}`
);
setImageSrc("/noImage.png");
}}
/>
</div>
);
};

export default CardImage;
Loading

0 comments on commit e8f414f

Please sign in to comment.