Skip to content

Commit

Permalink
Merge pull request #1077: Stricter types
Browse files Browse the repository at this point in the history
  • Loading branch information
victorlin authored Nov 20, 2024
2 parents e1df53f + 4982fa8 commit 1d8c627
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 28 deletions.
3 changes: 2 additions & 1 deletion static-site/.eslintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ extends:
- eslint:recommended
- plugin:react/recommended
- plugin:react-hooks/recommended
- plugin:@typescript-eslint/recommended
- plugin:@typescript-eslint/strict
# We use the recommended Next.js eslint configuration `next/core-web-vitals` as per
# <https://nextjs.org/docs/pages/building-your-application/configuring/eslint#core-web-vitals>
# As of April 2024, the extension simply adds the `@next/next/core-web-vitals` plugin:
Expand All @@ -27,6 +27,7 @@ ignorePatterns:
- public/

rules:
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: 'never' }]
react/prop-types: off # Remove this override once all props have been typed using PropTypes or TypeScript.
'@next/next/no-img-element': off # Remove this if we use next.js optimisations for <img>
'@next/next/no-html-link-for-pages': off
Expand Down
20 changes: 16 additions & 4 deletions static-site/app/blog/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ export function generateStaticParams(): BlogPostParams[] {
});
}

type PopulatedMetadata = Metadata & {
metadataBase: URL
openGraph: {
description: string
images: { url: string}[]
siteName: string
title: string
type: "website"
url: URL | string
}
}

// generate opengraph and other metadata tags
export async function generateMetadata({
params,
Expand All @@ -37,7 +49,7 @@ export async function generateMetadata({

// set up some defaults that are independent of the specific blog post
const baseUrl = new URL(siteUrl);
const metadata: Metadata = {
const metadata: PopulatedMetadata = {
metadataBase: baseUrl,
openGraph: {
description: siteTitleAlt,
Expand All @@ -61,9 +73,9 @@ export async function generateMetadata({

metadata.title = blogPost.title;
metadata.description = description;
metadata.openGraph!.description = description;
metadata.openGraph!.title = `${siteTitle}: ${blogPost.title}`;
metadata.openGraph!.url = `/blog/${blogPost.blogUrlName}`;
metadata.openGraph.description = description;
metadata.openGraph.title = `${siteTitle}: ${blogPost.title}`;
metadata.openGraph.url = `/blog/${blogPost.blogUrlName}`;
}

return metadata;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import React, { ErrorInfo, ReactNode } from "react";
import { ErrorContainer } from "../../pages/404";
import { ErrorContainer } from "../pages/404";

export class InternalError extends Error {
constructor(message: string) {
super(message);
}
}
export class InternalError extends Error {}

interface Props {
children: ReactNode;
Expand Down
18 changes: 16 additions & 2 deletions static-site/src/components/Groups/Tiles/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ import { UserContext } from "../../../layouts/userDataWrapper";
import { GroupTile } from "./types";
import { Group } from "../types";
import { ExpandableTiles } from "../../ExpandableTiles";
import { ErrorBoundary, InternalError } from "../../ErrorBoundary";


export const GroupTiles = () => {
return (
<ErrorBoundary>
<GroupTilesUnhandled />
</ErrorBoundary>
);
};

const GroupTilesUnhandled = () => {
const { visibleGroups } = useContext(UserContext);
return (
<ExpandableTiles
Expand All @@ -25,8 +34,13 @@ function createGroupTiles(groups: Group[], colors = [...theme.titleColors]): Gro
return groups
.sort((a, b) => a.name.localeCompare(b.name))
.map((group) => {
const groupColor = colors[0]!;
colors.push(colors.shift()!);
if (colors[0] === undefined) {
throw new InternalError("Colors are missing.");
}
const groupColor = colors[0];

// Rotate the colors
colors.push(colors.shift()!); // eslint-disable-line @typescript-eslint/no-non-null-assertion

const tile: GroupTile = {
img: "empty.png",
Expand Down
12 changes: 10 additions & 2 deletions static-site/src/components/ListResources/IndividualResource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MdHistory } from "react-icons/md";
import { SetModalResourceContext } from './Modal';
import { ResourceDisplayName, Resource, DisplayNamedResource } from './types';
import { IconType } from 'react-icons';
import { InternalError } from './errors';
import { InternalError } from '../ErrorBoundary';

export const LINK_COLOR = '#5097BA'
export const LINK_HOVER_COLOR = '#31586c'
Expand Down Expand Up @@ -139,9 +139,17 @@ export const IndividualResource = ({
throw new InternalError("ref must be defined and the parent must be a div (IndividualResourceContainer).");
}

// The type of ref.current.parentNode is ParentNode which does not have an
// offsetTop property. I don't think there is a way to appease the
// TypeScript compiler other than a type assertion. It is loosely coupled
// to the check above for parentNode.nodeName.
// Note: this doesn't strictly have to be a div, but that's what it is in
// current usage of the component at the time of writing.
const parentNode = ref.current.parentNode as HTMLDivElement // eslint-disable-line @typescript-eslint/consistent-type-assertions

/* The column CSS is great but doesn't allow us to know if an element is at
the top of its column, so we resort to JS */
if (ref.current.offsetTop===(ref.current.parentNode as HTMLDivElement).offsetTop) {
if (ref.current.offsetTop===parentNode.offsetTop) {
setTopOfColumn(true);
}
}, []);
Expand Down
2 changes: 1 addition & 1 deletion static-site/src/components/ListResources/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as d3 from "d3";
import { MdClose } from "react-icons/md";
import { dodge } from "./dodge";
import { Resource, VersionedResource } from './types';
import { InternalError } from './errors';
import { InternalError } from '../ErrorBoundary';

export const SetModalResourceContext = createContext<React.Dispatch<React.SetStateAction<Resource | undefined>> | null>(null);

Expand Down
2 changes: 1 addition & 1 deletion static-site/src/components/ListResources/ResourceGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IndividualResource, getMaxResourceWidth, TooltipWrapper, IconContainer,
ResourceLinkWrapper, ResourceLink, LINK_COLOR, LINK_HOVER_COLOR } from "./IndividualResource"
import { SetModalResourceContext } from "./Modal";
import { DisplayNamedResource, Group, QuickLink, Resource } from './types';
import { InternalError } from './errors';
import { InternalError } from '../ErrorBoundary';

const ResourceGroupHeader = ({
group,
Expand Down
9 changes: 7 additions & 2 deletions static-site/src/components/ListResources/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {ResourceModal, SetModalResourceContext} from "./Modal";
import { ExpandableTiles } from "../ExpandableTiles";
import { FilterTile, FilterOption, Group, QuickLink, Resource, ResourceListingInfo, SortMethod, convertVersionedResource } from './types';
import { HugeSpacer } from "../../layouts/generalComponents";
import { ErrorBoundary } from './errors';
import { ErrorBoundary, InternalError } from '../ErrorBoundary';

const LIST_ANCHOR = "list";

Expand Down Expand Up @@ -182,7 +182,12 @@ function SortOptions({sortMethod, changeSortMethod}: {
changeSortMethod: React.Dispatch<React.SetStateAction<SortMethod>>,
}) {
function onChangeValue(event: FormEvent<HTMLInputElement>): void {
changeSortMethod(event.currentTarget.value as SortMethod);
const sortMethod = event.currentTarget.value;
if (sortMethod !== "alphabetical" &&
sortMethod !== "lastUpdated") {
throw new InternalError(`Unhandled sort method: '${sortMethod}'`);
}
changeSortMethod(sortMethod);
}
return (
<SortContainer>
Expand Down
2 changes: 1 addition & 1 deletion static-site/src/components/ListResources/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InternalError } from "./errors";
import { InternalError } from "../ErrorBoundary";
import { Tile } from "../ExpandableTiles/types"

export interface FilterOption {
Expand Down
18 changes: 13 additions & 5 deletions static-site/src/components/ListResources/useDataFetch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { Group, Resource, ResourceListingInfo } from './types';
import { InternalError } from '../ErrorBoundary';


/**
Expand Down Expand Up @@ -65,8 +66,16 @@ function partitionByPathogen(
const sortedDates = [...dates].sort();

const nameParts = name.split('/');
// split() will always return at least 1 string
const groupName = nameParts[0]!;

if (nameParts[0] === undefined) {
throw new InternalError(`Name is not properly formatted: '${name}'`);
}

if (sortedDates[0] === undefined) {
throw new InternalError("Resource does not have any dates.");
}

const groupName = nameParts[0];

const resourceDetails: Resource = {
name,
Expand All @@ -77,14 +86,13 @@ function partitionByPathogen(
lastUpdated: sortedDates.at(-1),
};
if (versioned) {
resourceDetails.firstUpdated = sortedDates[0]!;
resourceDetails.firstUpdated = sortedDates[0];
resourceDetails.dates = sortedDates;
resourceDetails.nVersions = sortedDates.length;
resourceDetails.updateCadence = updateCadence(sortedDates.map((date)=> new Date(date)));
}

if (!store[groupName]) store[groupName] = [];
store[groupName]!.push(resourceDetails)
(store[groupName] ??= []).push(resourceDetails)

return store;
}, {});
Expand Down
1 change: 1 addition & 0 deletions static-site/src/components/splash/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ const Splash = () => {
</Styles.H1Small>

<BigSpacer/>
{/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */}
<ExpandableTiles tiles={featuredAnalyses as unknown as SplashTile[]} tileWidth={tileWidth} tileHeight={tileHeight} TileComponent={Tile} />
<Tooltip style={{fontSize: '1.6rem'}} id={tooltipId} />

Expand Down
6 changes: 3 additions & 3 deletions static-site/src/sections/group-members-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const GroupMembersPage = ({ groupName }: {groupName: string}) => {
roles = await rolesResponse.json();
members = await membersResponse.json();
} catch (err) {
const errorMessage = (err as Error).message
const errorMessage = err instanceof Error ? err.message : String(err)
if(!ignore) {
setErrorMessage({
title: "An error occurred when trying to fetch group membership data",
Expand Down Expand Up @@ -77,7 +77,7 @@ const GroupMembersPage = ({ groupName }: {groupName: string}) => {
<BigSpacer/>

{roles && members
? <MembersTable members={members as GroupMember[]} />
? <MembersTable members={members} />
: <splashStyles.H4>Fetching group members...</splashStyles.H4>}
</GenericPage>
)
Expand Down Expand Up @@ -150,7 +150,7 @@ export async function canViewGroupMembers(groupName: string) {
const allowedMethods = new Set(groupMemberOptions.headers.get("Allow")?.split(/\s*,\s*/));
return allowedMethods.has("GET");
} catch (err) {
const errorMessage = (err as Error).message
const errorMessage = err instanceof Error ? err.message : String(err)
console.error("Cannot check user permissions to view group members", errorMessage);
}
return false
Expand Down

0 comments on commit 1d8c627

Please sign in to comment.