From 6163414d17c2a3e52cca0a1ee28364e6f44e7670 Mon Sep 17 00:00:00 2001 From: Theo Sanderson Date: Tue, 20 Feb 2024 15:59:27 +0000 Subject: [PATCH] Add a "my sequences page" displaying released sequences (#1037) * wip * still types to fix * add link * fix types and more * remove download dialog * remove redundancies * Update my_sequences.astro * Update website/src/routes.ts Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> * Update website/src/pages/[organism]/my_sequences/[group].astro Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> * Update website/src/pages/[organism]/my_sequences/[group].astro Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> * refactor(website): use GroupManagementClient * refactor to reduce code duplication * improve error message * add back group functionality with better exclude option * lots of fixes * add underline to link * get user groups not all groups --------- Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> Co-authored-by: Fabian Engelniederhammer --- website/src/components/DataUploadForm.tsx | 6 +- .../MySequencesPage/GroupSelector.tsx | 27 +++++ .../components/SearchPage/SearchForm.spec.tsx | 3 +- .../src/components/SearchPage/SearchForm.tsx | 8 +- .../SearchPage/SearchPagination.tsx | 16 ++- website/src/components/SearchPage/Table.tsx | 23 +++- website/src/components/User/UserPage.astro | 3 + .../src/pages/[organism]/my_sequences.astro | 39 ++++++ .../[organism]/my_sequences/[group].astro | 111 ++++++++++++++++++ .../src/pages/[organism]/search/index.astro | 67 ++++------- website/src/routes.ts | 34 +++++- website/src/services/groupManagementClient.ts | 7 ++ .../utils/processParametersAndFetchSearch.ts | 83 +++++++++++++ .../[organism]/search => utils}/search.ts | 24 ++-- 14 files changed, 380 insertions(+), 71 deletions(-) create mode 100644 website/src/components/MySequencesPage/GroupSelector.tsx create mode 100644 website/src/pages/[organism]/my_sequences.astro create mode 100644 website/src/pages/[organism]/my_sequences/[group].astro create mode 100644 website/src/utils/processParametersAndFetchSearch.ts rename website/src/{pages/[organism]/search => utils}/search.ts (86%) diff --git a/website/src/components/DataUploadForm.tsx b/website/src/components/DataUploadForm.tsx index 5792d85f0e..8f2517964c 100644 --- a/website/src/components/DataUploadForm.tsx +++ b/website/src/components/DataUploadForm.tsx @@ -150,7 +150,11 @@ const InnerDataUploadForm = ({

Loading groups...

) : (

- No group found. Please join or create a group. + No group found. Please join or{' '} + + create a group + + .

) ) : ( diff --git a/website/src/components/MySequencesPage/GroupSelector.tsx b/website/src/components/MySequencesPage/GroupSelector.tsx new file mode 100644 index 0000000000..2717fc07fc --- /dev/null +++ b/website/src/components/MySequencesPage/GroupSelector.tsx @@ -0,0 +1,27 @@ +import { type FC } from 'react'; + +import { routes } from '../../routes'; + +type GroupSelectorProps = { + groupNames: string[]; + selectedGroupName: string; + organism: string; +}; +export const GroupSelector: FC = ({ groupNames, selectedGroupName, organism }) => { + return ( + + ); +}; diff --git a/website/src/components/SearchPage/SearchForm.spec.tsx b/website/src/components/SearchPage/SearchForm.spec.tsx index d874c45f82..6edec0008b 100644 --- a/website/src/components/SearchPage/SearchForm.spec.tsx +++ b/website/src/components/SearchPage/SearchForm.spec.tsx @@ -5,7 +5,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest'; import { SearchForm } from './SearchForm'; import { testConfig, testOrganism } from '../../../vitest.setup.ts'; -import { routes } from '../../routes.ts'; +import { routes, SEARCH } from '../../routes.ts'; import type { MetadataFilter } from '../../types/config.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; @@ -40,6 +40,7 @@ function renderSearchForm( filters={searchFormFilters} initialMutationFilter={{}} clientConfig={clientConfig} + classOfSearchPage={SEARCH} referenceGenomesSequenceNames={referenceGenomesSequenceNames} /> , diff --git a/website/src/components/SearchPage/SearchForm.tsx b/website/src/components/SearchPage/SearchForm.tsx index 76d54d8c1f..7fe13f57ad 100644 --- a/website/src/components/SearchPage/SearchForm.tsx +++ b/website/src/components/SearchPage/SearchForm.tsx @@ -14,7 +14,7 @@ import { PangoLineageField } from './fields/PangoLineageField'; import { getClientLogger } from '../../clientLogger.ts'; import { getLapisUrl } from '../../config.ts'; import { useOffCanvas } from '../../hooks/useOffCanvas'; -import { routes, navigateToSearchPage } from '../../routes.ts'; +import { routes, navigateToSearchLikePage, type ClassOfSearchPageType } from '../../routes.ts'; import type { MetadataFilter, MutationFilter } from '../../types/config.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; @@ -29,6 +29,8 @@ interface SearchFormProps { initialMutationFilter: MutationFilter; clientConfig: ClientConfig; referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; + classOfSearchPage: ClassOfSearchPageType; + group?: string; } const clientLogger = getClientLogger('SearchForm'); @@ -39,6 +41,8 @@ export const SearchForm: FC = ({ initialMutationFilter, clientConfig, referenceGenomesSequenceNames, + classOfSearchPage, + group, }) => { const [fieldValues, setFieldValues] = useState<(MetadataFilter & { label: string })[]>( filters.map((filter) => ({ @@ -66,7 +70,7 @@ export const SearchForm: FC = ({ event.preventDefault(); setIsLoading(true); const searchableFieldValues = fieldValues.filter((field) => !(field.notSearchable ?? false)); - navigateToSearchPage(organism, searchableFieldValues, mutationFilter); + navigateToSearchLikePage(organism, classOfSearchPage, group, searchableFieldValues, mutationFilter); }; const resetSearch = async () => { diff --git a/website/src/components/SearchPage/SearchPagination.tsx b/website/src/components/SearchPage/SearchPagination.tsx index fea78f0c49..038e45bb69 100644 --- a/website/src/components/SearchPage/SearchPagination.tsx +++ b/website/src/components/SearchPage/SearchPagination.tsx @@ -1,7 +1,7 @@ import { Pagination as MUIPagination } from '@mui/material'; import type { FC } from 'react'; -import { navigateToSearchPage } from '../../routes'; +import { navigateToSearchLikePage, type ClassOfSearchPageType } from '../../routes'; import type { MetadataFilter, MutationFilter } from '../../types/config.ts'; import type { OrderBy } from '../../types/lapis.ts'; @@ -12,6 +12,8 @@ type SearchPaginationProps = { orderBy: OrderBy; organism: string; page: number; + classOfSearchPage: ClassOfSearchPageType; + group?: string; }; export const SearchPagination: FC = ({ @@ -21,13 +23,23 @@ export const SearchPagination: FC = ({ orderBy, organism, page, + classOfSearchPage, + group, }) => { return ( { - navigateToSearchPage(organism, metadataFilter, mutationFilter, newPage, orderBy); + navigateToSearchLikePage( + organism, + classOfSearchPage, + group, + metadataFilter, + mutationFilter, + newPage, + orderBy, + ); }} color='primary' variant='outlined' diff --git a/website/src/components/SearchPage/Table.tsx b/website/src/components/SearchPage/Table.tsx index f2b60d7780..4b2db93b86 100644 --- a/website/src/components/SearchPage/Table.tsx +++ b/website/src/components/SearchPage/Table.tsx @@ -1,12 +1,11 @@ import { capitalCase } from 'change-case'; import type { FC, ReactElement } from 'react'; -import { routes, navigateToSearchPage } from '../../routes.ts'; +import { routes, navigateToSearchLikePage, type ClassOfSearchPageType } from '../../routes.ts'; import type { MetadataFilter, MutationFilter, Schema } from '../../types/config.ts'; import type { OrderBy } from '../../types/lapis.ts'; import MdiTriangle from '~icons/mdi/triangle'; import MdiTriangleDown from '~icons/mdi/triangle-down'; - export type TableSequenceData = { [key: string]: string | number | null; }; @@ -19,9 +18,21 @@ type TableProps = { mutationFilter: MutationFilter; page: number; orderBy: OrderBy; + classOfSearchPage: ClassOfSearchPageType; + group?: string; }; -export const Table: FC = ({ organism, data, schema, metadataFilter, mutationFilter, page, orderBy }) => { +export const Table: FC = ({ + organism, + data, + schema, + metadataFilter, + mutationFilter, + page, + orderBy, + classOfSearchPage, + group, +}) => { const primaryKey = schema.primaryKey; const columns = schema.tableColumns.map((field) => ({ @@ -32,18 +43,18 @@ export const Table: FC = ({ organism, data, schema, metadataFilter, const handleSort = (field: string) => { if (orderBy.field === field) { if (orderBy.type === 'ascending') { - navigateToSearchPage(organism, metadataFilter, mutationFilter, page, { + navigateToSearchLikePage(organism, classOfSearchPage, group, metadataFilter, mutationFilter, page, { field, type: 'descending', }); } else { - navigateToSearchPage(organism, metadataFilter, mutationFilter, page, { + navigateToSearchLikePage(organism, classOfSearchPage, group, metadataFilter, mutationFilter, page, { field, type: 'ascending', }); } } else { - navigateToSearchPage(organism, metadataFilter, mutationFilter, page, { + navigateToSearchLikePage(organism, classOfSearchPage, group, metadataFilter, mutationFilter, page, { field, type: 'ascending', }); diff --git a/website/src/components/User/UserPage.astro b/website/src/components/User/UserPage.astro index 1534796c30..16f8746dee 100644 --- a/website/src/components/User/UserPage.astro +++ b/website/src/components/User/UserPage.astro @@ -37,6 +37,9 @@ const keycloakLogoutUrl = (await getKeycloakClient()).endSessionUrl({ + ) } diff --git a/website/src/pages/[organism]/my_sequences.astro b/website/src/pages/[organism]/my_sequences.astro new file mode 100644 index 0000000000..46105a6590 --- /dev/null +++ b/website/src/pages/[organism]/my_sequences.astro @@ -0,0 +1,39 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro'; +import { routes } from '../../routes'; +import { GroupManagementClient } from '../../services/groupManagementClient'; +import { getAccessToken } from '../../utils/getAccessToken'; + +const accessToken = getAccessToken(Astro.locals.session)!; + +const groupsResult = await GroupManagementClient.create().getGroupsOfUser(accessToken); + +const organism = Astro.params.organism; +let noGroups = false; +let errorMessage = ''; +if (groupsResult.isOk()) { + if (groupsResult.value.length > 0) { + return Astro.redirect(routes.mySequencesPage(organism, groupsResult.value[0].groupName)); + } + noGroups = true; +} else { + errorMessage = groupsResult.error.detail; +} +--- + + +
+

+ { + noGroups && ( +

+ To access sequence-management pages your account needs to be part of a group. Please{' '} + create a group, or ask an existing group admin to + add you to their group. +
+ ) + } + {errorMessage &&
{errorMessage}
} +

+
+
diff --git a/website/src/pages/[organism]/my_sequences/[group].astro b/website/src/pages/[organism]/my_sequences/[group].astro new file mode 100644 index 0000000000..8d75bf27f0 --- /dev/null +++ b/website/src/pages/[organism]/my_sequences/[group].astro @@ -0,0 +1,111 @@ +--- +import { GroupSelector } from '../../../components/MySequencesPage/GroupSelector'; +import { SearchForm } from '../../../components/SearchPage/SearchForm'; +import { SearchPagination } from '../../../components/SearchPage/SearchPagination'; +import { Table } from '../../../components/SearchPage/Table'; +import ErrorBox from '../../../components/common/ErrorBox.astro'; +import BaseLayout from '../../../layouts/BaseLayout.astro'; +import { MY_SEQUENCES } from '../../../routes'; +import { GroupManagementClient } from '../../../services/groupManagementClient'; +import { pageSize } from '../../../settings'; +import { getAccessToken } from '../../../utils/getAccessToken'; +import { processParametersAndFetchSearch } from '../../../utils/processParametersAndFetchSearch'; + +const group = Astro.params.group!; +const accessToken = getAccessToken(Astro.locals.session)!; +const groupsResult = await GroupManagementClient.create().getGroupsOfUser(accessToken); +if (groupsResult.isOk() === false) { + return new Response(undefined, { + status: 500, + message: 'Failed to get groups', + }); +} +const groups = groupsResult.value; + +const groupNames = groups.map((group: { groupName: string }) => group.groupName); +const groupExists = groupNames.includes(group); +if (groupExists === false) { + return new Response(undefined, { + status: 404, + }); +} + +const { + cleanedOrganism, + organism, + data, + page, + metadataFilter, + mutationFilter, + referenceGenomesSequenceNames, + schema, + clientConfig, + orderBy, +} = await processParametersAndFetchSearch(Astro, group); +--- + + +

Viewing sequences for group {group}

+ { + groupNames.length > 1 && ( + <> + Choose group: + + + ) + } + +
+
+ +
+ { + data.match( + (data) => ( +
+
+ Search returned {data.totalCount.toLocaleString()} + sequence{data.totalCount === 1 ? '' : 's'} +
+ + +
+ +
+ + ), + (error) => , + ) + } + + diff --git a/website/src/pages/[organism]/search/index.astro b/website/src/pages/[organism]/search/index.astro index c5ef076dc3..a24649124b 100644 --- a/website/src/pages/[organism]/search/index.astro +++ b/website/src/pages/[organism]/search/index.astro @@ -1,59 +1,29 @@ --- -import { - getData, - getReferenceGenomesSequenceNames, - getMetadataFilters, - getMutationFilter, - getOrderBy, - addHiddenFilters, -} from './search'; -import { cleanOrganism } from '../../../components/Navigation/cleanOrganism'; import { DownloadDialog } from '../../../components/SearchPage/DownloadDialog/DownloadDialog'; import { SearchForm } from '../../../components/SearchPage/SearchForm'; import { SearchPagination } from '../../../components/SearchPage/SearchPagination'; import { Table } from '../../../components/SearchPage/Table'; import ErrorBox from '../../../components/common/ErrorBox.astro'; -import { getLapisUrl, getRuntimeConfig, getSchema } from '../../../config'; import BaseLayout from '../../../layouts/BaseLayout.astro'; -import { hiddenDefaultSearchFilters, pageSize } from '../../../settings'; - -const organism = Astro.params.organism!; -const { organism: cleanedOrganism } = cleanOrganism(organism); -const schema = getSchema(organism); -const clientConfig = getRuntimeConfig().public; -const lapisUrl = getLapisUrl(clientConfig, organism); -let postParams = new URLSearchParams(); - -if (Astro.request.method === 'POST') { - const formData = await Astro.request.text(); - const params = new URLSearchParams(formData); - const decoded = Object.fromEntries(params); - const paramsString = decoded.searchQuery; - postParams = new URLSearchParams(paramsString); -} - -const getSearchParams = (field: string): string => { - let value = Astro.url.searchParams.get(field); - if (value === null && postParams.get(field) !== null) { - value = postParams.get(field); - } - return value ?? ''; -}; - -const metadataFilter = addHiddenFilters(getMetadataFilters(getSearchParams, organism), hiddenDefaultSearchFilters); -const mutationFilter = getMutationFilter(Astro.url.searchParams); - -const pageParam = getSearchParams('page'); -const page = pageParam !== '' ? Number.parseInt(pageParam, 10) : 1; -const offset = (page - 1) * pageSize; -const orderBy = getOrderBy(getSearchParams, schema.defaultOrderBy, schema.defaultOrder); - -const referenceGenomesSequenceNames = getReferenceGenomesSequenceNames(organism); - -const data = await getData(organism, metadataFilter, mutationFilter, offset, pageSize, orderBy); +import { SEARCH } from '../../../routes'; +import { pageSize } from '../../../settings'; +import { processParametersAndFetchSearch } from '../../../utils/processParametersAndFetchSearch'; +const { + cleanedOrganism, + organism, + data, + page, + metadataFilter, + mutationFilter, + lapisUrl, + referenceGenomesSequenceNames, + schema, + clientConfig, + orderBy, +} = await processParametersAndFetchSearch(Astro); --- - +

Search

@@ -63,6 +33,7 @@ const data = await getData(organism, metadataFilter, mutationFilter, offset, pag initialMutationFilter={mutationFilter} clientConfig={clientConfig} referenceGenomesSequenceNames={referenceGenomesSequenceNames} + classOfSearchPage={SEARCH} client:only='react' />
@@ -89,6 +60,7 @@ const data = await getData(organism, metadataFilter, mutationFilter, offset, pag mutationFilter={mutationFilter} page={page} orderBy={orderBy} + classOfSearchPage={SEARCH} client:load /> @@ -101,6 +73,7 @@ const data = await getData(organism, metadataFilter, mutationFilter, offset, pag mutationFilter={mutationFilter} orderBy={orderBy} organism={organism} + classOfSearchPage={SEARCH} />
diff --git a/website/src/routes.ts b/website/src/routes.ts index 4980b5b141..3996b037c3 100644 --- a/website/src/routes.ts +++ b/website/src/routes.ts @@ -4,13 +4,17 @@ import type { OrderBy } from './types/lapis.ts'; import { getAccessionVersionString } from './utils/extractAccessionVersion.ts'; const approxMaxUrlLengthForSearch = 1900; +export const SEARCH = 'SEARCH'; +export const MY_SEQUENCES = 'MY_SEQUENCES'; export const routes = { aboutPage: () => '/about', apiDocumentationPage: () => '/api_documentation', + whereYouCreateAGroup: () => '/user', governancePage: () => '/governance', statusPage: () => '/status', organismStartPage: (organism: string) => `/${organism}`, + mySequencesWithoutGroup: (organism: string) => `/${organism}/my_sequences`, searchPage: ( organism: string, metadataFilter: Filter[] = [], @@ -22,6 +26,19 @@ export const routes = { organism, `/search?${buildSearchParams(metadataFilter, mutationFilter, page, orderBy).toString()}`, ), + + mySequencesPage: ( + organism: string, + group: string, + metadataFilter: FilterValue[] = [], + mutationFilter: MutationFilter = {}, + page: number | undefined = undefined, + orderBy?: OrderBy, + ) => + withOrganism( + organism, + `/my_sequences/${group}?${buildSearchParams(metadataFilter, mutationFilter, page, orderBy).toString()}`, + ), sequencesDetailsPage: (organism: string, accessionVersion: AccessionVersion | string) => `/${organism}/seq/${getAccessionVersionString(accessionVersion)}`, sequencesVersionsPage: (organism: string, accessionVersion: AccessionVersion | string) => @@ -56,8 +73,13 @@ export const routes = { notFoundPage: () => `/404`, logout: () => '/logout', }; -export const navigateToSearchPage = ( + +export type ClassOfSearchPageType = 'SEARCH' | 'MY_SEQUENCES'; + +export const navigateToSearchLikePage = ( organism: string, + classOfSearchPage: ClassOfSearchPageType, + group: string | undefined, metadataFilter: FilterValue[] = [], mutationFilter: MutationFilter = {}, page?: number, @@ -66,7 +88,12 @@ export const navigateToSearchPage = ( const paramsString = buildSearchParams(metadataFilter, mutationFilter, page, orderBy).toString(); if (paramsString.length < approxMaxUrlLengthForSearch) { - location.href = routes.searchPage(organism, metadataFilter, mutationFilter, page, orderBy); + if (classOfSearchPage === SEARCH) { + location.href = routes.searchPage(organism, metadataFilter, mutationFilter, page, orderBy); + } + if (classOfSearchPage === MY_SEQUENCES) { + location.href = routes.mySequencesPage(organism, group!, metadataFilter, mutationFilter, page, orderBy); + } } else { const form = document.createElement('form'); const addField = (name: string, value: string) => { @@ -77,7 +104,8 @@ export const navigateToSearchPage = ( form.appendChild(field); }; form.method = 'POST'; - form.action = routes.searchPage(organism); + form.action = + classOfSearchPage === SEARCH ? routes.searchPage(organism) : routes.mySequencesPage(organism, group!); addField('searchQuery', paramsString); addField('organism', organism); diff --git a/website/src/services/groupManagementClient.ts b/website/src/services/groupManagementClient.ts index ff34e82660..3883fece99 100644 --- a/website/src/services/groupManagementClient.ts +++ b/website/src/services/groupManagementClient.ts @@ -2,6 +2,7 @@ import { groupManagementApi } from './groupManagementApi.ts'; import { ZodiosWrapperClient } from './zodiosWrapperClient.ts'; import { getRuntimeConfig } from '../config.ts'; import { getInstanceLogger } from '../logger.ts'; +import { createAuthorizationHeader } from '../utils/createAuthorizationHeader.ts'; export class GroupManagementClient extends ZodiosWrapperClient { public static create( @@ -16,4 +17,10 @@ export class GroupManagementClient extends ZodiosWrapperClient { + const valueFromGet = astro.url.searchParams.get(field); + const value = valueFromGet !== '' ? valueFromGet : postParams.get(field); + return value ?? ''; + }; + + let hiddenSearchFeatures = hiddenDefaultSearchFilters; + if (groupForMySequences !== undefined) { + hiddenSearchFeatures = [ + ...hiddenSearchFeatures, + { name: GROUP_FIELD, filterValue: groupForMySequences, type: 'string' as const, notSearchable: true }, + ]; + } + + const metadataFilter = addHiddenFilters( + getMetadataFilters( + getSearchParams, + organism, + groupForMySequences !== undefined + ? { + exclude: [GROUP_FIELD], + } + : {}, + ), + hiddenSearchFeatures, + ); + const mutationFilter = getMutationFilter(astro.url.searchParams); + + const pageParam = getSearchParams('page'); + const page = pageParam !== '' ? Number.parseInt(pageParam, 10) : 1; + const offset = (page - 1) * pageSize; + const orderBy = getOrderBy(getSearchParams, schema.defaultOrderBy, schema.defaultOrder); + + const referenceGenomesSequenceNames = getReferenceGenomesSequenceNames(organism); + + const data = await getData(organism, metadataFilter, mutationFilter, offset, pageSize, orderBy); + + return { + organism, + cleanedOrganism, + data, + page, + metadataFilter, + mutationFilter, + lapisUrl, + referenceGenomesSequenceNames, + schema, + clientConfig, + orderBy, + }; +} diff --git a/website/src/pages/[organism]/search/search.ts b/website/src/utils/search.ts similarity index 86% rename from website/src/pages/[organism]/search/search.ts rename to website/src/utils/search.ts index a4dc07cb47..193812584b 100644 --- a/website/src/pages/[organism]/search/search.ts +++ b/website/src/utils/search.ts @@ -1,13 +1,12 @@ import { ok, Result } from 'neverthrow'; -import type { TableSequenceData } from '../../../components/SearchPage/Table.tsx'; -import { getReferenceGenomes, getSchema } from '../../../config.ts'; -import { LapisClient } from '../../../services/lapisClient.ts'; -import type { ProblemDetail } from '../../../types/backend.ts'; -import type { MetadataFilter, MutationFilter } from '../../../types/config.ts'; -import { type LapisBaseRequest, type OrderBy, type OrderByType, orderByType } from '../../../types/lapis.ts'; -import type { ReferenceGenomesSequenceNames } from '../../../types/referencesGenomes.ts'; - +import type { TableSequenceData } from '../components/SearchPage/Table.tsx'; +import { getReferenceGenomes, getSchema } from '../config.ts'; +import { LapisClient } from '../services/lapisClient.ts'; +import type { ProblemDetail } from '../types/backend.ts'; +import type { MetadataFilter, MutationFilter } from '../types/config.ts'; +import { type LapisBaseRequest, type OrderBy, type OrderByType, orderByType } from '../types/lapis.ts'; +import type { ReferenceGenomesSequenceNames } from '../types/referencesGenomes.ts'; export type SearchResponse = { data: TableSequenceData[]; totalCount: number; @@ -73,12 +72,19 @@ export const getData = async ( }); }; -export const getMetadataFilters = (getSearchParams: (param: string) => string, organism: string): MetadataFilter[] => { +export const getMetadataFilters = ( + getSearchParams: (param: string) => string, + organism: string, + options: { exclude?: string[] } = {}, +): MetadataFilter[] => { const schema = getSchema(organism); return schema.metadata.flatMap((metadata) => { if (metadata.notSearchable === true) { return []; } + if (options.exclude && options.exclude.includes(metadata.name)) { + return []; + } if (metadata.type === 'date' || metadata.type === 'timestamp') { const metadataFrom = {