Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ _templates
# favicons
/public/favicons/*

# og images
/public/og/*

/files/**/out/

#Python
Expand Down
45 changes: 45 additions & 0 deletions app/common/meta/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { PageMeta } from "./entities";

export const CONTENT_PAGE_META: Record<string, PageMeta> = {
"beta-announcement": {
pageDescription:
"AnVIL Data Explorer beta launch announcement and new features.",
pageTitle: "Beta Announcement",
},
"ga-announcement": {
pageDescription: "AnVIL Data Explorer general availability announcement.",
pageTitle: "GA Announcement",
},
guides: {
pageDescription:
"Guides for downloading and accessing data from the AnVIL Data Explorer.",
pageTitle: "Guides",
},
"guides/data-download-options": {
pageDescription:
"Overview of data download options available in the AnVIL Data Explorer.",
pageTitle: "Data Download Options",
},
"guides/data-download-via-curl": {
pageDescription: "Download datasets using the curl command line tool.",
pageTitle: "Data Download via curl",
},
"guides/individual-file-download": {
pageDescription:
"Download individual files directly from the AnVIL Data Explorer.",
pageTitle: "Individual File Download",
},
"guides/tsv-file-manifest-download": {
pageDescription:
"Export a TSV file manifest with download URLs from the AnVIL Data Explorer.",
pageTitle: "TSV File Manifest Download",
},
privacy: {
pageDescription: "Privacy policy for this data explorer.",
pageTitle: "Privacy Policy",
},
"terms-of-service": {
pageDescription: "Terms of service for this data explorer.",
pageTitle: "Terms of Service",
},
};
4 changes: 4 additions & 0 deletions app/common/meta/entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface PageMeta {
pageDescription: string;
pageTitle: string;
}
2 changes: 2 additions & 0 deletions app/components/common/OgMeta/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEFAULT_DESCRIPTION =
"Explore datasets, donors, biosamples, and files in this cloud-based genomic data platform.";
74 changes: 74 additions & 0 deletions app/components/common/OgMeta/ogMeta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import NextHead from "next/head";
import { useRouter } from "next/router";
import { JSX } from "react";
import type { OgMetaProps } from "./types";

/**
* Builds the canonical path from the router's asPath, stripping query and hash.
* @param asPath - The router's asPath value.
* @returns clean path.
*/
function buildPath(asPath: string): string {
return asPath.split("?")[0].split("#")[0];
}

/**
* Builds the OG title from the page title and app title.
* @param appTitle - The application title.
* @param pageTitle - The page-specific title.
* @returns formatted title.
*/
function buildTitle(appTitle: string, pageTitle?: string | null): string {
if (pageTitle && pageTitle !== appTitle) {
return `${pageTitle} - ${appTitle}`;
}
return appTitle;
}

/**
* Renders Open Graph and Twitter meta tags for rich link sharing.
* @param props - The component props.
* @param props.appTitle - The application title.
* @param props.browserURL - The site's base URL.
* @param props.defaultDescription - Fallback description when no page description is provided.
* @param props.pageDescription - Page-specific description.
* @param props.pageTitle - Page-specific title.
* @returns head element with meta tags.
*/
export const OgMeta = ({
appTitle,
browserURL,
defaultDescription,
pageDescription,
pageTitle,
}: OgMetaProps): JSX.Element => {
const { asPath } = useRouter();
const description = pageDescription || defaultDescription;
const image = `${browserURL}/og/og-image.png`;
const path = buildPath(asPath);
const title = buildTitle(appTitle, pageTitle);
const url = `${browserURL}${path}`;
return (
<NextHead>
<meta key="description" content={description} name="description" />
<meta
key="og:description"
content={description}
property="og:description"
/>
<meta key="og:image" content={image} property="og:image" />
<meta key="og:image:height" content="630" property="og:image:height" />
<meta key="og:image:width" content="1200" property="og:image:width" />
<meta key="og:site_name" content={appTitle} property="og:site_name" />
<meta key="og:title" content={title} property="og:title" />
<meta key="og:type" content="website" property="og:type" />
<meta key="og:url" content={url} property="og:url" />
<meta
key="twitter:card"
content="summary_large_image"
name="twitter:card"
/>
<meta key="twitter:image" content={image} name="twitter:image" />
</NextHead>
);
};
7 changes: 7 additions & 0 deletions app/components/common/OgMeta/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface OgMetaProps {
appTitle: string;
browserURL: string;
defaultDescription: string;
pageDescription?: string | null;
pageTitle?: string | null;
}
4 changes: 3 additions & 1 deletion app/content/common/contentPages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {

export async function getContentStaticProps(
context: GetStaticPropsContext,
pageTitle: string
pageTitle: string,
pageDescription?: string
): Promise<GetStaticPropsResult<ContentProps>> {
const slug = getSlug(context);
const contentPathname = getContentPathname();
Expand All @@ -40,6 +41,7 @@ export async function getContentStaticProps(
props: {
layoutStyle: LAYOUT_STYLE_NO_CONTRAST_DEFAULT,
mdxSource,
pageDescription,
pageTitle,
slug,
},
Expand Down
1 change: 1 addition & 0 deletions app/content/common/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface AnchorProps {
export interface ContentProps {
layoutStyle?: LayoutStyle;
mdxSource: MDXRemoteSerializeResult | null;
pageDescription?: string;
pageTitle: string;
slug: string[] | null;
}
12 changes: 11 additions & 1 deletion pages/[entityListType]/[...params].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ interface PageUrl extends ParsedUrlQuery {
export interface EntityDetailPageProps extends AzulEntityStaticResponse {
entityListType: string;
override?: Override;
pageDescription?: string | null;
pageTitle?: string | null;
}

/**
Expand Down Expand Up @@ -264,7 +266,15 @@ export const getStaticProps: GetStaticProps<AzulEntityStaticResponse> = async ({

if (!entityConfig || !entityId) return { notFound: true };

const props: EntityDetailPageProps = { entityListType };
const { label } = entityConfig;
const pageTitle = typeof label === "string" ? label : null;
const props: EntityDetailPageProps = {
entityListType,
pageDescription: pageTitle
? `View ${pageTitle.toLowerCase()} details and access data.`
: null,
pageTitle,
};

// Process entity override props.
processEntityOverrideProps(entityConfig, entityListType, entityId, props);
Expand Down
13 changes: 7 additions & 6 deletions pages/[entityListType]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ interface PageUrl extends ParsedUrlQuery {

interface ListPageProps extends AzulEntitiesStaticResponse {
entityListType: string;
pageTitle?: string;
pageDescription?: string | null;
pageTitle?: string | null;
}

/**
Expand Down Expand Up @@ -98,12 +99,12 @@ export const getStaticProps: GetStaticProps<
const { exploreMode, label } = entityConfig;
const { fetchAllEntities } = getEntityService(entityConfig, undefined); // Determine the type of fetch, either from an API endpoint or a TSV.

let pageTitle;
if (typeof label === "string") {
pageTitle = label;
}
const pageTitle = typeof label === "string" ? label : null;
const pageDescription = pageTitle
? `Browse and explore ${pageTitle.toLowerCase()}.`
: null;

const props: ListPageProps = { entityListType, pageTitle };
const props: ListPageProps = { entityListType, pageDescription, pageTitle };

// Seed database.
if (exploreMode === EXPLORE_MODE.CS_FETCH_CS_FILTERING) {
Expand Down
21 changes: 19 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { ThemeProvider as EmotionThemeProvider } from "@emotion/react";
import { createTheme, CssBaseline, Theme, ThemeProvider } from "@mui/material";
import { createBreakpoints } from "@mui/system";
import { deepmerge } from "@mui/utils";
import { DEFAULT_DESCRIPTION } from "app/components/common/OgMeta/constants";
import { OgMeta } from "app/components/common/OgMeta/ogMeta";
import { config } from "app/config/config";
import { FEATURES } from "app/shared/entities";
import { NextPage } from "next";
Expand All @@ -37,6 +39,7 @@ const FEATURE_FLAGS = Object.values(FEATURES);
const SESSION_TIMEOUT = 15 * 60 * 1000; // 15 minutes

export interface PageProps extends AzulEntitiesStaticResponse {
pageDescription?: string;
pageTitle?: string;
}

Expand All @@ -53,11 +56,18 @@ setFeatureFlags(FEATURE_FLAGS);
function MyApp({ Component, pageProps }: AppPropsWithComponent): JSX.Element {
// Set up the site configuration, layout and theme.
const appConfig = config();
const { analytics, layout, redirectRootToPath, themeOptions } = appConfig;
const {
analytics,
appTitle,
browserURL,
layout,
redirectRootToPath,
themeOptions,
} = appConfig;
const { gtmAuth, gtmId, gtmPreview } = analytics || {};
const { floating, footer, header } = layout || {};
const theme = createAppTheme(themeOptions);
const { entityListType, pageTitle } = pageProps as PageProps;
const { entityListType, pageDescription, pageTitle } = pageProps as PageProps;
const Main = Component.Main || DXMain;

// Initialize Google Tag Manager.
Expand All @@ -72,6 +82,13 @@ function MyApp({ Component, pageProps }: AppPropsWithComponent): JSX.Element {
<ThemeProvider theme={theme}>
<DXConfigProvider config={appConfig} entityListType={entityListType}>
<Head pageTitle={pageTitle} />
<OgMeta
appTitle={appTitle}
browserURL={browserURL}
defaultDescription={DEFAULT_DESCRIPTION}
pageDescription={pageDescription}
pageTitle={pageTitle}
/>
<CssBaseline />
<ServicesProvider>
<SystemStatusProvider>
Expand Down
8 changes: 7 additions & 1 deletion pages/beta-announcement/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContentView } from "@databiosphere/findable-ui/lib/views/ContentView/co
import { GetStaticProps, InferGetStaticPropsType } from "next";
import { MDXRemote } from "next-mdx-remote";
import { JSX } from "react";
import { CONTENT_PAGE_META } from "../../app/common/meta/constants";
import { Content } from "../../app/components/Layout/components/Content/content";
import { MDX_COMPONENTS } from "../../app/content/common/constants";
import { getContentStaticProps } from "../../app/content/common/contentPages";
Expand All @@ -11,7 +12,12 @@ import NotFoundPage from "../404";
const slug = ["beta-announcement"];

export const getStaticProps: GetStaticProps = async () => {
return getContentStaticProps({ params: { slug } }, "Beta Announcement");
const meta = CONTENT_PAGE_META["beta-announcement"];
return getContentStaticProps(
{ params: { slug } },
meta.pageTitle,
meta.pageDescription
);
};

const Page = ({
Expand Down
8 changes: 7 additions & 1 deletion pages/data-dictionary/[dictionary]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ export const getStaticProps = async (
context: GetStaticPropsContext<PageUrlParams>
): Promise<GetStaticPropsResult<PageUrlParams>> => {
const { dictionary } = context.params as PageUrlParams;
return { props: { dictionary } };
return {
props: {
dictionary,
pageDescription: "Browse the data dictionary and metadata schema.",
pageTitle: "Data Dictionary",
},
};
};

export const getStaticPaths: GetStaticPaths = async () => {
Expand Down
1 change: 1 addition & 0 deletions pages/export/biodata-catalyst.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Export selected data to NHLBI BioData Catalyst.",
pageTitle: "Export to NHLBI BioData Catalyst",
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/cancer-genomics-cloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Export selected data to Cancer Genomics Cloud.",
pageTitle: "Export to Cancer Genomics Cloud",
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/cavatica.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Export selected data to CAVATICA for analysis.",
pageTitle: "Export to CAVATICA",
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/download-manifest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Request a file manifest for your selected data.",
pageTitle: "Request File Manifest",
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/export-to-terra.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Export selected data to Terra for analysis.",
pageTitle: "Export to Terra",
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/get-curl-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { hasNRESConsentGroup } from "../../app/viewModelBuilders/azul/anvil-cmg/
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Download selected data using the curl command.",
pageTitle: 'Download Selected Data Using "curl"',
},
};
Expand Down
1 change: 1 addition & 0 deletions pages/export/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JSX } from "react";
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
pageDescription: "Choose an export method for your selected data.",
pageTitle: "Choose Export Method",
},
};
Expand Down
5 changes: 4 additions & 1 deletion pages/ga-announcement/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContentView } from "@databiosphere/findable-ui/lib/views/ContentView/co
import { GetStaticProps, InferGetStaticPropsType } from "next";
import { MDXRemote } from "next-mdx-remote";
import { JSX } from "react";
import { CONTENT_PAGE_META } from "../../app/common/meta/constants";
import { Content } from "../../app/components/Layout/components/Content/content";
import { MDX_COMPONENTS } from "../../app/content/common/constants";
import { getContentStaticProps } from "../../app/content/common/contentPages";
Expand All @@ -11,9 +12,11 @@ import NotFoundPage from "../404";
const slug = ["ga-announcement"];

export const getStaticProps: GetStaticProps = async () => {
const meta = CONTENT_PAGE_META["ga-announcement"];
return getContentStaticProps(
{ params: { slug } },
"General Availability Announcement"
meta.pageTitle,
meta.pageDescription
);
};

Expand Down
Loading
Loading