From e01598e7dd66d93e1ed20a180b29a7986eb9dce5 Mon Sep 17 00:00:00 2001 From: MillenniumFalconMechanic Date: Tue, 14 Apr 2026 11:39:35 -0700 Subject: [PATCH 1/3] feat: always show curl export option in non-production environments #4767 Co-Authored-By: Claude Opus 4.5 --- app/config/utils.ts | 10 +++++ .../anvil-cmg/common/viewModelBuilders.tsx | 37 +++++++++++++------ pages/[entityListType]/[...params].tsx | 10 ++++- pages/export/get-curl-command.tsx | 18 +++++++++ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/app/config/utils.ts b/app/config/utils.ts index d5080a15c..f4a817797 100644 --- a/app/config/utils.ts +++ b/app/config/utils.ts @@ -1,5 +1,15 @@ import { SelectCategoryValue } from "@databiosphere/findable-ui/lib/common/entities"; +/** + * Returns true if the current environment is production. + * Determined by checking if NEXT_PUBLIC_SITE_CONFIG contains "prod". + * @returns true if production environment. + */ +export function isProductionEnvironment(): boolean { + const config = process.env.NEXT_PUBLIC_SITE_CONFIG ?? ""; + return config.includes("prod"); +} + /** * Returns select category value with formatted label. * @param formatLabel - Function to format label. diff --git a/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx b/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx index 7021e3125..a886319b8 100644 --- a/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx +++ b/app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders.tsx @@ -109,6 +109,7 @@ import { METADATA_KEY } from "../../../../components/Index/common/entities"; import { getPluralizedMetadataLabel } from "../../../../components/Index/common/indexTransformer"; import { SUMMARY_DISPLAY_TEXT } from "./summaryMapper/constants"; import { mapExportSummary } from "./summaryMapper/summaryMapper"; +import { isProductionEnvironment } from "../../../../config/utils"; /** * Build props for activity type BasicCell component from the given activities response. @@ -1790,15 +1791,13 @@ function isFileManifestSummaryFileCountValid( } /** - * Returns true if the dataset has NRES or Unrestricted access consent group. - * @param viewContext - View context. - * @returns true if the dataset has NRES or Unrestricted access consent group. + * Returns true if the file manifest state includes NRES or Unrestricted access consent group. + * @param fileManifestState - File manifest state. + * @returns true if NRES or Unrestricted access consent group is present. */ -export function isNRESConsentGroup( - viewContext: ViewContext +export function hasNRESConsentGroup( + fileManifestState: FileManifestState ): boolean { - const { fileManifestState } = viewContext; - const facet = findFacet( fileManifestState.filesFacets, ANVIL_CMG_CATEGORY_KEY.DATASET_CONSENT_GROUP @@ -1811,6 +1810,17 @@ export function isNRESConsentGroup( return termsByName.has("NRES") || termsByName.has("Unrestricted access"); } +/** + * Returns true if the dataset has NRES or Unrestricted access consent group. + * @param viewContext - View context. + * @returns true if the dataset has NRES or Unrestricted access consent group. + */ +export function isNRESConsentGroup( + viewContext: ViewContext +): boolean { + return hasNRESConsentGroup(viewContext.fileManifestState); +} + /** * Returns true if the response is ready (for use) for the given authorization state. * The response is ready when the response is no longer loading (loading is false). @@ -1882,7 +1892,8 @@ export const renderWhenUnAuthenticated = ( }; /** - * Renders cohort curl download component when the current query includes NRES or Unrestricted access consent groups. + * Renders cohort curl download component when the current query includes NRES or Unrestricted access consent groups, + * or when the environment is non-production. * @param _ - Unused. * @param viewContext - View context. * @returns model to be used as props for the ConditionalComponent component. @@ -1891,11 +1902,14 @@ export const renderCohortCurlDownload = ( _: unknown, viewContext: ViewContext ): ComponentProps => { - return { isIn: isNRESConsentGroup(viewContext) }; + return { + isIn: !isProductionEnvironment() || isNRESConsentGroup(viewContext), + }; }; /** - * Renders dataset curl download components when the given dataset has NRES consent group. + * Renders dataset curl download components when the given dataset has NRES consent group, + * or when the environment is non-production. * @param datasetsResponse - Response model return from datasets API. * @returns model to be used as props for the ConditionalComponent component. */ @@ -1903,7 +1917,8 @@ export const renderDatasetCurlDownload = ( datasetsResponse: DatasetsResponse ): React.ComponentProps => { return { - isIn: isDatasetNRESConsentGroup(datasetsResponse), + isIn: + !isProductionEnvironment() || isDatasetNRESConsentGroup(datasetsResponse), }; }; diff --git a/pages/[entityListType]/[...params].tsx b/pages/[entityListType]/[...params].tsx index 804919442..e32beb92a 100644 --- a/pages/[entityListType]/[...params].tsx +++ b/pages/[entityListType]/[...params].tsx @@ -35,6 +35,7 @@ import { ROUTES } from "../../site-config/anvil-cmg/dev/export/routes"; import { getConsentGroup } from "../../app/apis/azul/anvil-cmg/common/transformers"; import { DatasetsResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; +import { isProductionEnvironment } from "../../app/config/utils"; const setOfProcessedIds = new Set(); @@ -66,8 +67,13 @@ const EntityDetailPage = (props: EntityDetailPageProps): JSX.Element => { const { query } = useRouter(); if (!props.entityListType) return <>; if (props.override) return ; - // Curl download requires NRES consent group (AnVIL only) - if (isAnVIL && isCurlDownloadRoute(query) && !isNRESDataset(props.data)) { + // Curl download requires NRES consent group (AnVIL production only) + if ( + isAnVIL && + isProductionEnvironment() && + isCurlDownloadRoute(query) && + !isNRESDataset(props.data) + ) { return ; } if (isChooseExportView(query)) return ; diff --git a/pages/export/get-curl-command.tsx b/pages/export/get-curl-command.tsx index f9d520594..6314aba17 100644 --- a/pages/export/get-curl-command.tsx +++ b/pages/export/get-curl-command.tsx @@ -1,6 +1,11 @@ import { JSX } from "react"; import { ExportMethodView } from "@databiosphere/findable-ui/lib/views/ExportMethodView/exportMethodView"; import { GetStaticProps } from "next"; +import { useFileManifestState } from "@databiosphere/findable-ui/lib/hooks/useFileManifestState"; +import { useConfig } from "@databiosphere/findable-ui/lib/hooks/useConfig"; +import NextError from "next/error"; +import { isProductionEnvironment } from "../../app/config/utils"; +import { hasNRESConsentGroup } from "../../app/viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; export const getStaticProps: GetStaticProps = async () => { return { @@ -15,6 +20,19 @@ export const getStaticProps: GetStaticProps = async () => { * @returns download curl command view component. */ const GetCurlCommandPage = (): JSX.Element => { + const { config } = useConfig(); + const { fileManifestState } = useFileManifestState(); + const isAnVIL = config.appTitle?.includes("AnVIL"); + + // Curl download requires NRES consent group (AnVIL production only) + if ( + isAnVIL && + isProductionEnvironment() && + !hasNRESConsentGroup(fileManifestState) + ) { + return ; + } + return ; }; From 7f84642b55cdd5b0fb09fd4516cc6b6bc9e29600 Mon Sep 17 00:00:00 2001 From: MillenniumFalconMechanic Date: Tue, 14 Apr 2026 15:09:18 -0700 Subject: [PATCH 2/3] refactor: extract shared NRES consent group check #4767 - Add isNRESOrUnrestrictedAccess to transformers.ts - Update isNRESDataset to check for both NRES and Unrestricted access - Remove isNRESConsentGroup wrapper, use hasNRESConsentGroup directly Co-Authored-By: Claude Opus 4.5 --- .../azul/anvil-cmg/common/transformers.ts | 12 +++++++++++ .../DownloadSection/downloadSection.tsx | 4 ++-- app/config/utils.ts | 16 +++++++++++++-- .../anvil-cmg/common/viewModelBuilders.tsx | 20 ++++++------------- pages/[entityListType]/[...params].tsx | 11 ++++++---- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/app/apis/azul/anvil-cmg/common/transformers.ts b/app/apis/azul/anvil-cmg/common/transformers.ts index 780560f45..cd013f375 100644 --- a/app/apis/azul/anvil-cmg/common/transformers.ts +++ b/app/apis/azul/anvil-cmg/common/transformers.ts @@ -71,6 +71,18 @@ export function getConsentGroup(response: DatasetsResponse): string[] { return processAggregatedOrArrayValue(response.datasets, "consent_group"); } +/** + * Returns true if consent groups include NRES or Unrestricted access. + * @param consentGroups - Array of consent group strings. + * @returns true if NRES or Unrestricted access is present. + */ +export function isNRESOrUnrestrictedAccess(consentGroups: string[]): boolean { + return ( + consentGroups.includes("NRES") || + consentGroups.includes("Unrestricted access") + ); +} + /** * Maps biosample type from an aggregated biosamples value returned from endpoints other than index/biosamples. * @param response - Response model return from Azul that includes aggregated biosamples. diff --git a/app/components/Export/components/AnVILExplorer/components/ExportCohort/components/DownloadSection/downloadSection.tsx b/app/components/Export/components/AnVILExplorer/components/ExportCohort/components/DownloadSection/downloadSection.tsx index 5890a101a..beb0d2392 100644 --- a/app/components/Export/components/AnVILExplorer/components/ExportCohort/components/DownloadSection/downloadSection.tsx +++ b/app/components/Export/components/AnVILExplorer/components/ExportCohort/components/DownloadSection/downloadSection.tsx @@ -1,7 +1,7 @@ import { TYPOGRAPHY_PROPS } from "@databiosphere/findable-ui/lib/styles/common/mui/typography"; import { Stack, Typography } from "@mui/material"; import type { JSX } from "react"; -import { isNRESConsentGroup } from "../../../../../../../../viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; +import { hasNRESConsentGroup } from "../../../../../../../../viewModelBuilders/azul/anvil-cmg/common/viewModelBuilders"; import { Props } from "./types"; /** @@ -11,7 +11,7 @@ import { Props } from "./types"; * @returns Download section title and description. */ export const DownloadSection = ({ viewContext }: Props): JSX.Element => { - const isNRES = isNRESConsentGroup(viewContext); + const isNRES = hasNRESConsentGroup(viewContext.fileManifestState); return ( -): boolean { - return hasNRESConsentGroup(viewContext.fileManifestState); + return isNRESOrUnrestrictedAccess(consentGroups); } /** @@ -1903,7 +1893,9 @@ export const renderCohortCurlDownload = ( viewContext: ViewContext ): ComponentProps => { return { - isIn: !isProductionEnvironment() || isNRESConsentGroup(viewContext), + isIn: + !isProductionEnvironment() || + hasNRESConsentGroup(viewContext.fileManifestState), }; }; diff --git a/pages/[entityListType]/[...params].tsx b/pages/[entityListType]/[...params].tsx index e32beb92a..07250b4be 100644 --- a/pages/[entityListType]/[...params].tsx +++ b/pages/[entityListType]/[...params].tsx @@ -33,7 +33,10 @@ import { useConfig } from "@databiosphere/findable-ui/lib/hooks/useConfig"; import NextError from "next/error"; import { ROUTES } from "../../site-config/anvil-cmg/dev/export/routes"; -import { getConsentGroup } from "../../app/apis/azul/anvil-cmg/common/transformers"; +import { + getConsentGroup, + isNRESOrUnrestrictedAccess, +} from "../../app/apis/azul/anvil-cmg/common/transformers"; import { DatasetsResponse } from "../../app/apis/azul/anvil-cmg/common/responses"; import { isProductionEnvironment } from "../../app/config/utils"; @@ -109,14 +112,14 @@ function isCurlDownloadRoute(query: ParsedUrlQuery): boolean { } /** - * Returns true if the dataset has NRES consent group. + * Returns true if the dataset has NRES or Unrestricted access consent group. * @param data - Entity response data. - * @returns True if the dataset has NRES consent group. + * @returns True if the dataset has NRES or Unrestricted access consent group. */ function isNRESDataset(data: AzulEntityStaticResponse | undefined): boolean { if (!data) return false; const consentGroups = getConsentGroup(data as DatasetsResponse); - return consentGroups.includes("NRES"); + return isNRESOrUnrestrictedAccess(consentGroups); } /** From 32b09f57fb58245591b3690cd331ccbff409ff81 Mon Sep 17 00:00:00 2001 From: Mim Hastie Date: Tue, 14 Apr 2026 15:17:05 -0700 Subject: [PATCH 3/3] fix: remove dead code #4767 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- app/config/utils.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/config/utils.ts b/app/config/utils.ts index b7ebd539b..79467d407 100644 --- a/app/config/utils.ts +++ b/app/config/utils.ts @@ -1,17 +1,5 @@ import { SelectCategoryValue } from "@databiosphere/findable-ui/lib/common/entities"; -/** - * Returns true if consent groups include NRES or Unrestricted access. - * @param consentGroups - Array of consent group strings. - * @returns true if NRES or Unrestricted access is present. - */ -export function hasNRESOrUnrestrictedAccess(consentGroups: string[]): boolean { - return ( - consentGroups.includes("NRES") || - consentGroups.includes("Unrestricted access") - ); -} - /** * Returns true if the current environment is production. * Determined by checking if NEXT_PUBLIC_SITE_CONFIG ends with "-prod".