Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display withdrawn project page (#3695) #3784

Merged
merged 5 commits into from
Nov 22, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Set of entity status.
*/
export enum ENTITY_STATUS {
DEPRECATED = "DEPRECATED",
DUPLICATE = "DUPLICATE",
INGEST_IN_PROGRESS = "INGEST_IN_PROGRESS",
LIVE = "LIVE",
WITHDRAWN = "WITHDRAWN",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ButtonPrimary } from "@clevercanary/data-explorer-ui/lib/components/common/Button/components/ButtonPrimary/buttonPrimary";
import { AlertIcon } from "@clevercanary/data-explorer-ui/lib/components/common/CustomIcon/components/AlertIcon/alertIcon";
import { SectionActions } from "@clevercanary/data-explorer-ui/lib/components/common/Section/section.styles";
import {
PRIORITY,
StatusIcon,
} from "@clevercanary/data-explorer-ui/lib/components/common/StatusIcon/statusIcon";
import { Override } from "@clevercanary/data-explorer-ui/lib/config/entities";
import {
TEXT_BODY_LARGE_400,
TEXT_HEADING_XLARGE,
} from "@clevercanary/data-explorer-ui/lib/theme/common/typography";
import { Link as MLink, Typography } from "@mui/material";
import Link from "next/link";
import { useRouter } from "next/router";
import React from "react";
import { Notice, Section, SectionContent } from "../../entityGuard.styles";

interface EntityDeprecatedProps {
override: Override;
}

export const EntityDeprecated = ({
override,
}: EntityDeprecatedProps): JSX.Element => {
const router = useRouter();
const { supersededBy } = override || {};
const title = supersededBy
? "Project Relocated"
: "Project Permanently Removed";
return (
<Notice>
<Section>
<StatusIcon priority={PRIORITY.MEDIUM} StatusIcon={AlertIcon} />
<SectionContent>
<Typography component="h2" variant={TEXT_HEADING_XLARGE}>
{title}
</Typography>
<Typography variant={TEXT_BODY_LARGE_400}>
{supersededBy ? (
<>
The project you are requesting has been permanently moved and
may be accessed{" "}
<MLink
onClick={(): void => {
router.push({
pathname: router.pathname,
query: { ...router.query, params: [supersededBy] },
});
}}
>
here
</MLink>
.
</>
) : (
<>The project you are requesting has been permanently removed.</>
)}
</Typography>
</SectionContent>
<SectionActions>
<Link href="/" passHref>
<ButtonPrimary href="passHref">To Homepage</ButtonPrimary>
</Link>
</SectionActions>
</Section>
</Notice>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ButtonPrimary } from "@clevercanary/data-explorer-ui/lib/components/common/Button/components/ButtonPrimary/buttonPrimary";
import { AlertIcon } from "@clevercanary/data-explorer-ui/lib/components/common/CustomIcon/components/AlertIcon/alertIcon";
import { SectionActions } from "@clevercanary/data-explorer-ui/lib/components/common/Section/section.styles";
import {
PRIORITY,
StatusIcon,
} from "@clevercanary/data-explorer-ui/lib/components/common/StatusIcon/statusIcon";
import { ANCHOR_TARGET } from "@clevercanary/data-explorer-ui/lib/components/Links/common/entities";
import { Override } from "@clevercanary/data-explorer-ui/lib/config/entities";
import {
TEXT_BODY_LARGE_400,
TEXT_HEADING_XLARGE,
} from "@clevercanary/data-explorer-ui/lib/theme/common/typography";
import { Link as MLink, Typography } from "@mui/material";
import Link from "next/link";
import React from "react";
import { Notice, Section, SectionContent } from "../../entityGuard.styles";

interface EntityWithdrawnProps {
override: Override;
}

export const EntityWithdrawn = ({
override,
}: EntityWithdrawnProps): JSX.Element => {
const { redirectUrl } = override || {};
return (
<Notice>
<Section>
<StatusIcon priority={PRIORITY.MEDIUM} StatusIcon={AlertIcon} />
<SectionContent>
<Typography component="h2" variant={TEXT_HEADING_XLARGE}>
Project Withdrawn
</Typography>
<Typography component="div" variant={TEXT_BODY_LARGE_400}>
<p>
The project you are requesting has been withdrawn from the HCA
Data Portal due to a GDPR request.
</p>
{redirectUrl && (
<>
<p>
The project can now be accessed at:{" "}
<MLink
href={redirectUrl}
rel="noreferrer"
target={ANCHOR_TARGET.BLANK}
>
{redirectUrl}
</MLink>
</p>
</>
)}
</Typography>
</SectionContent>
<SectionActions>
<Link href="/" passHref>
<ButtonPrimary href="passHref">To Homepage</ButtonPrimary>
</Link>
</SectionActions>
</Section>
</Notice>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Section as DXSection } from "@clevercanary/data-explorer-ui/lib/components/common/Section/section.styles";
import { mediaTabletUp } from "@clevercanary/data-explorer-ui/lib/styles/common/mixins/breakpoints";
import styled from "@emotion/styled";

export const Notice = styled.div`
margin: 0 auto;
max-width: 648px;
padding: 40px 20px;
width: 100%;
`;

export const Section = styled(DXSection)`
align-items: center;
padding: 0;

${mediaTabletUp} {
padding: 0;
}
`;

export const SectionContent = styled.div`
text-align: center;

.MuiTypography-text-heading-xlarge {
margin-top: -8px;
}

.MuiLink-root {
cursor: pointer;
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Override } from "@clevercanary/data-explorer-ui/lib/config/entities";
import Router from "next/router";
import React, { Fragment, useEffect } from "react";
import { ENTITY_STATUS } from "./common/entities";
import { EntityDeprecated } from "./components/EntityDeprecated/entityDeprecated";
import { EntityWithdrawn } from "./components/EntityWithdrawn/entityWithdrawn";

interface EntityGuardProps {
override: Override;
}

export const EntityGuard = ({ override }: EntityGuardProps): JSX.Element => {
const { duplicateOf } = override;
const viewMode = getEntityViewMode(override);

// Redirect if duplicate entry.
useEffect(() => {
if (duplicateOf) {
Router.push(duplicateOf);
}
}, [duplicateOf]);

return (
<Fragment>
{viewMode === ENTITY_STATUS.DEPRECATED && (
<EntityDeprecated override={override} />
)}
{viewMode === ENTITY_STATUS.DUPLICATE && <div>Redirecting...</div>}
{viewMode === ENTITY_STATUS.WITHDRAWN && (
<EntityWithdrawn override={override} />
)}
</Fragment>
);
};

/**
* Return the view mode for the entity, depending on its current status.
* @param override - Override.
* @returns view mode.
*/
function getEntityViewMode(override: Override): ENTITY_STATUS {
if (override.deprecated) {
return ENTITY_STATUS.DEPRECATED;
}
if (override.duplicateOf) {
return ENTITY_STATUS.DUPLICATE;
}
if (override.withdrawn) {
return ENTITY_STATUS.WITHDRAWN;
}
return ENTITY_STATUS.LIVE;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StaticImageProps } from "@clevercanary/data-explorer-ui/lib/components/common/StaticImage/staticImage";
import { Override } from "@clevercanary/data-explorer-ui/lib/config/entities";
import {
ContributorResponse,
PublicationResponse,
Expand All @@ -25,18 +26,12 @@ export interface AnalysisPortal {
url: string;
}

export interface ProjectEdit {
export interface ProjectEdit extends Override {
analysisPortals?: AnalysisPortal[];
contributors?: Partial<ContributorResponse>[];
deprecated?: boolean;
duplicateOf?: string;
entryId: string;
projectShortname?: string;
publications?: Pick<
PublicationResponse,
"publicationTitle" | "publicationUrl"
>[];
redirectUrl?: string;
supersededBy?: string;
withdrawn?: boolean;
}
6 changes: 3 additions & 3 deletions explorer/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build-anvil-db": "esrun anvil-catalog/build-anvil-catalog.ts",
"build-ncpi-db": "esrun ncpi-catalog/build-ncpi-catalog.ts",
"build-dug-db": "esrun ncpi-catalog-dug/build-ncpi-catalog-dug.ts",
"update-crdc-source": "esrun ncpi-catalog/update-crdc-source.ts",
"update-bdc-source": "esrun ncpi-catalog/update-bdc-source.ts",
"build-ncpi-db": "esrun ncpi-catalog/build-ncpi-catalog.ts",
"update-anvil-source": "esrun ncpi-catalog/update-anvil-source.ts",
"update-bdc-source": "esrun ncpi-catalog/update-bdc-source.ts",
"update-crdc-source": "esrun ncpi-catalog/update-crdc-source.ts",
"update-kfdrc-source": "esrun ncpi-catalog/update-kfdrc-source.ts",
"update-all-ncpi-sources": "npm run update-crdc-source && npm run update-bdc-source && npm run update-anvil-source && npm run update-kfdrc-source"
},
Expand Down
69 changes: 68 additions & 1 deletion explorer/pages/[entityListType]/[...params].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import {
PARAMS_INDEX_TAB,
PARAMS_INDEX_UUID,
} from "@clevercanary/data-explorer-ui/lib/common/constants";
import { EntityConfig } from "@clevercanary/data-explorer-ui/lib/config/entities";
import {
EntityConfig,
Override,
} from "@clevercanary/data-explorer-ui/lib/config/entities";
import { getEntityConfig } from "@clevercanary/data-explorer-ui/lib/config/utils";
import { getEntityService } from "@clevercanary/data-explorer-ui/lib/hooks/useEntityService";
import { database } from "@clevercanary/data-explorer-ui/lib/utils/database";
Expand All @@ -12,6 +15,7 @@ import { config } from "app/config/config";
import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from "next";
import { ParsedUrlQuery } from "querystring";
import React from "react";
import { EntityGuard } from "../../app/components/Detail/components/EntityGuard/entityGuard";
import { readFile } from "../../app/utils/tsvParser";

interface PageUrl extends ParsedUrlQuery {
Expand All @@ -21,6 +25,7 @@ interface PageUrl extends ParsedUrlQuery {

export interface EntityDetailPageProps extends AzulEntityStaticResponse {
entityListType: string;
override?: Override;
}

/**
Expand All @@ -31,9 +36,40 @@ export interface EntityDetailPageProps extends AzulEntityStaticResponse {
*/
const EntityDetailPage = (props: EntityDetailPageProps): JSX.Element => {
if (!props.entityListType) return <></>;
if (props.override) return <EntityGuard override={props.override} />;
return <EntityDetailView {...props} />;
};

/**
* Returns the override for the given entity ID.
* @param overrides - Overrides.
* @param entityId - Entity ID.
* @returns returns the override for the given entity ID.
*/
function findOverride(
overrides: Override[],
entityId?: string
): Override | undefined {
if (!entityId) {
return;
}
return overrides.find(({ entryId }) => entryId === entityId);
}

/**
* Returns true if the entity is a special case e.g. an "override".
* @param override - Override.
* @returns true if the entity is an override.
*/
function isOverride(override: Override): boolean {
return Boolean(
override.deprecated ||
override.duplicateOf ||
override.supersededBy ||
override.withdrawn
);
}

/**
* Seed database.
* @param entityListType - Entity list type.
Expand Down Expand Up @@ -105,6 +141,20 @@ export const getStaticPaths: GetStaticPaths<PageUrl> = async () => {
});
});
}

// process entity overrides
if (entityConfig.overrides) {
for (const override of entityConfig.overrides) {
if (isOverride(override)) {
resultParams.push({
params: {
entityListType: entityConfig.route,
params: [override.entryId],
},
});
}
}
}
return resultParams;
})
);
Expand Down Expand Up @@ -135,6 +185,23 @@ export const getStaticProps: GetStaticProps<AzulEntityStaticResponse> = async ({

const props: EntityDetailPageProps = { entityListType: entityListType };

// If there is a corresponding override for the given page, grab the override values from the override file and return as props.
if (entityConfig.overrides) {
const override = findOverride(
entityConfig.overrides,
params?.params?.[PARAMS_INDEX_UUID]
);
if (override && isOverride(override)) {
props.override = override;
if (override.duplicateOf) {
props.override.duplicateOf = `/${entityListType}/${override.duplicateOf}`;
}
return {
props,
};
}
}

// If the entity detail view is to be "statically loaded", we need to seed the database (for retrieval of the entity), or
// fetch the entity detail from API.
if (entityConfig.detail.staticLoad) {
Expand Down
Loading
Loading