Skip to content

Commit

Permalink
feat: request access via duos in addition to dbgap (#4127) (#4362)
Browse files Browse the repository at this point in the history
  • Loading branch information
frano-m authored Feb 4, 2025
1 parent 59d2d7b commit 97b8cdb
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 13 deletions.
1 change: 1 addition & 0 deletions app/apis/azul/anvil-cmg/common/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface DatasetEntity {
consent_group: (string | null)[];
dataset_id: string;
description?: string;
duos_id: string | null;
registered_identifier: (string | null)[];
title: string;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
ButtonProps,
ListItemTextProps,
MenuProps,
SvgIconProps,
} from "@mui/material";
import {
TEXT_BODY_500,
TEXT_BODY_SMALL_400_2_LINES,
} from "@databiosphere/findable-ui/lib/theme/common/typography";

export const BUTTON_PROPS: ButtonProps = {
color: "primary",
variant: "contained",
};

export const LIST_ITEM_TEXT_PROPS: ListItemTextProps = {
primaryTypographyProps: { variant: TEXT_BODY_500 },
secondaryTypographyProps: { variant: TEXT_BODY_SMALL_400_2_LINES },
};

export const MENU_PROPS: Partial<MenuProps> = {
variant: "menu",
};

export const SVG_ICON_PROPS: SvgIconProps = {
fontSize: "small",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Button } from "@mui/material";
import styled from "@emotion/styled";
import { css } from "@emotion/react";
import { primaryDark } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors";
import { DropdownMenu } from "@databiosphere/findable-ui/lib/components/common/DropdownMenu/dropdownMenu";

interface Props {
open?: boolean;
}

export const StyledDropdownMenu = styled(DropdownMenu)`
.MuiPaper-menu {
max-width: 324px;
.MuiListItemText-root {
display: grid;
gap: 4px;
white-space: normal;
}
}
`;

export const StyledButton = styled(Button, {
shouldForwardProp: (prop) => prop !== "open",
})<Props>`
padding-right: 8px;
.MuiButton-endIcon {
margin-left: -6px;
}
${(props) =>
props.open &&
css`
background-color: ${primaryDark(props)};
`}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Props } from "./types";
import { ListItemText, MenuItem } from "@mui/material";
import { Actions } from "@databiosphere/findable-ui/lib/components/Layout/components/BackPage/components/BackPageHero/components/Actions/actions";
import { CallToActionButton } from "@databiosphere/findable-ui/lib/components/common/Button/components/CallToActionButton/callToActionButton";
import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded";
import { StyledButton, StyledDropdownMenu } from "./requestAccess.styles";
import {
ANCHOR_TARGET,
REL_ATTRIBUTE,
} from "@databiosphere/findable-ui/lib/components/Links/common/entities";
import {
BUTTON_PROPS,
LIST_ITEM_TEXT_PROPS,
MENU_PROPS,
SVG_ICON_PROPS,
} from "./constants";
import { getRequestAccessOptions } from "./utils";

export const RequestAccess = ({
datasetsResponse,
}: Props): JSX.Element | null => {
const options = getRequestAccessOptions(datasetsResponse);
// If there are no request access options, return null.
if (options.length === 0) return null;
// If there is only one request access option, render a CallToActionButton.
if (options.length === 1)
return (
<Actions>
<CallToActionButton
callToAction={{ label: "Request Access", url: options[0].href }}
/>
</Actions>
);
// Otherwise, render a dropdown menu for multiple request access options.
return (
<Actions>
<StyledDropdownMenu
{...MENU_PROPS}
button={(props) => (
<StyledButton
{...BUTTON_PROPS}
endIcon={<ArrowDropDownRoundedIcon {...SVG_ICON_PROPS} />}
{...props}
>
Request Access
</StyledButton>
)}
>
{({ closeMenu }): JSX.Element[] => [
...options.map(({ href, primary, secondary }, i) => (
<MenuItem
key={i}
component="a"
href={href}
rel={REL_ATTRIBUTE.NO_OPENER_NO_REFERRER}
target={ANCHOR_TARGET.BLANK}
onClick={closeMenu}
>
<ListItemText
{...LIST_ITEM_TEXT_PROPS}
primary={primary}
secondary={secondary}
/>
</MenuItem>
)),
]}
</StyledDropdownMenu>
</Actions>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DatasetsResponse } from "../../../../../../apis/azul/anvil-cmg/common/responses";

export interface Props {
datasetsResponse: DatasetsResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { DatasetsResponse } from "../../../../../../apis/azul/anvil-cmg/common/responses";
import { takeArrayValueAt } from "../../../../../../viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders";
import {
processEntityArrayValue,
processEntityValue,
} from "../../../../../../apis/azul/common/utils";
import { LABEL } from "@databiosphere/findable-ui/lib/apis/azul/common/entities";
import { ListItemTextProps } from "@mui/material";

/**
* Generates a list of request access menu options based on the provided dataset response.
* This function extracts identifiers (DUOS ID and dbGaP ID) from the datasets response and returns an array of menu option objects.
* Each menu option contains a link `href` and title `primary` and description text `secondary`, to be used in Material UI's `MenuItem` and `ListItemText` component.
* @param datasetsResponse - Response model return from datasets API.
* @returns menu option objects with `href`, `primary`, and `secondary` properties.
*/
export function getRequestAccessOptions(
datasetsResponse: DatasetsResponse
): (Pick<ListItemTextProps, "primary" | "secondary"> & { href: string })[] {
// Get the dbGaP ID and DUOS ID from the datasets response.
const dbGapId = takeArrayValueAt(
processEntityArrayValue(
datasetsResponse.datasets,
"registered_identifier",
LABEL.EMPTY
),
0
);
const duosId = processEntityValue(
datasetsResponse.datasets,
"duos_id",
LABEL.EMPTY
);
const options = [];
if (duosId) {
// If a DUOS ID is present, add a menu option for DUOS.
options.push({
href: `https://duos.org/dataset/${duosId}`,
primary: "DUOS",
secondary:
"Request access via DUOS, which streamlines data access for NHGRI-sponsored studies, both registered and unregistered in dbGaP.",
});
}
if (dbGapId) {
// If a dbGaP ID is present, add a menu option for dbGaP.
options.push({
href: `https://dbgap.ncbi.nlm.nih.gov/aa/wga.cgi?adddataset=${dbGapId}`,
primary: "dbGaP",
secondary:
"Request access via the dbGaP Authorized Access portal for studies registered in dbGaP, following the standard data access process.",
});
}
return options;
}
34 changes: 21 additions & 13 deletions app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
ChipProps as MChipProps,
FadeProps as MFadeProps,
} from "@mui/material";
import React from "react";
import React, { ReactNode } from "react";
import {
ANVIL_CMG_CATEGORY_KEY,
ANVIL_CMG_CATEGORY_LABEL,
Expand Down Expand Up @@ -112,6 +112,7 @@ import { Unused, Void } from "../../../common/entities";
import { SUMMARY_DISPLAY_TEXT } from "./summaryMapper/constants";
import { mapExportSummary } from "./summaryMapper/summaryMapper";
import { ExportEntity } from "app/components/Export/components/AnVILExplorer/components/ExportEntity/exportEntity";
import { RequestAccess } from "../../../../components/Detail/components/AnVILCMG/components/RequestAccess/requestAccess";

/**
* Build props for activity type BasicCell component from the given activities response.
Expand Down Expand Up @@ -512,6 +513,7 @@ export const buildDatasetHero = (
datasetsResponse: DatasetsResponse
): React.ComponentProps<typeof C.BackPageHero> => {
return {
actions: getDatasetRequestAccess(datasetsResponse),
breadcrumbs: getDatasetBreadcrumbs(datasetsResponse),
callToAction: getDatasetCallToAction(datasetsResponse),
title: getDatasetTitle(datasetsResponse),
Expand Down Expand Up @@ -1079,10 +1081,7 @@ function getDatasetCallToAction(
): CallToAction | undefined {
const isReady = isResponseReady(datasetsResponse);
const isAccessGranted = isDatasetAccessible(datasetsResponse);
const registeredIdentifier = getDatasetRegisteredIdentifier(datasetsResponse);
if (!isReady) {
return;
}
if (!isReady) return;
// Display export button if user is authorized to access the dataset.
if (isAccessGranted) {
return {
Expand All @@ -1091,14 +1090,6 @@ function getDatasetCallToAction(
url: `/datasets/${getDatasetEntryId(datasetsResponse)}/export`,
};
}
// Display request access button if user is not authorized to access the dataset.
if (registeredIdentifier === LABEL.UNSPECIFIED) {
return {
label: "Request Access",
target: ANCHOR_TARGET.BLANK,
url: `https://dbgap.ncbi.nlm.nih.gov/aa/wga.cgi?adddataset=${registeredIdentifier}`,
};
}
// Otherwise, display nothing.
}

Expand All @@ -1116,6 +1107,23 @@ export function getDatasetRegisteredIdentifier(
);
}

/**
* Returns the `actions` prop for the Hero component from the given datasets response.
* @param datasetsResponse - Response model return from datasets API.
* @returns react node to be used as the `actions` props for the Hero component.
*/
function getDatasetRequestAccess(
datasetsResponse: DatasetsResponse
): ReactNode {
const isReady = isResponseReady(datasetsResponse);
const isAccessGranted = isDatasetAccessible(datasetsResponse);
if (!isReady) return null;
// Display nothing if user is authorized to access the dataset.
if (isAccessGranted) return null;
// Display request access button if user is not authorized to access the dataset.
return RequestAccess({ datasetsResponse });
}

/**
* Returns StatusBadge component props from the given datasets response.
* @param datasetsResponse - Response model return from datasets API.
Expand Down

0 comments on commit 97b8cdb

Please sign in to comment.