From 76d78f2d229969249b8b3c980b6717fea84fceeb Mon Sep 17 00:00:00 2001 From: Fran McDade Date: Thu, 14 Mar 2024 17:30:38 +1000 Subject: [PATCH] feat: update entity detail page to view entities from other catalogs (#573) (#590) * feat: update entity detail page to view entities from other catalogs (#573) * refactor: catalog response entity (#573) --------- Co-authored-by: Fran McDade --- .../src/apis/azul/common/entities.ts | 23 ++++++++ .../src/entity/api/service.ts | 57 ++++++++++++++----- .../src/entity/common/client.ts | 4 +- .../src/entity/service/model.ts | 8 ++- .../src/hooks/useFetchEntity.tsx | 2 +- .../src/hooks/useUpdateURLCatalogParam.ts | 21 +++++++ .../EntityDetailView/entityDetailView.tsx | 2 + 7 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 packages/data-explorer-ui/src/hooks/useUpdateURLCatalogParam.ts diff --git a/packages/data-explorer-ui/src/apis/azul/common/entities.ts b/packages/data-explorer-ui/src/apis/azul/common/entities.ts index 26f8d3a9..fcbccff5 100644 --- a/packages/data-explorer-ui/src/apis/azul/common/entities.ts +++ b/packages/data-explorer-ui/src/apis/azul/common/entities.ts @@ -2,12 +2,35 @@ * Set of end point paths accepted by Azul. */ export enum APIEndpoints { + CATALOGS = "/index/catalogs", FETCH = "/fetch", // Required in path for entity matrix downloads and direct file downloads FILES = "/files", INDEX_STATUS = "/health/progress", SUMMARY = "/summary", } +/** + * Model of response returned from /index/catalogs API endpoint. + */ +export interface AzulCatalogResponse { + catalogs: AzulCatalogs; + default_catalog: string; +} + +/** + * Model of catalog returned from Azul catalogs endpoint (e.g. index/catalogs). + */ +export interface AzulCatalog { + internal: boolean; +} + +/** + * Model of catalogs returned from Azul catalogs endpoint (e.g. index/catalogs). + */ +export interface AzulCatalogs { + [key: string]: AzulCatalog; +} + /** * Model of index (list) responses from Azul, such as projects (index/projects), samples (index/samples) and * files (index/files). diff --git a/packages/data-explorer-ui/src/entity/api/service.ts b/packages/data-explorer-ui/src/entity/api/service.ts index d7aace40..6027766c 100644 --- a/packages/data-explorer-ui/src/entity/api/service.ts +++ b/packages/data-explorer-ui/src/entity/api/service.ts @@ -3,6 +3,8 @@ */ // TODO move to Azul APIs section import { + APIEndpoints, + AzulCatalogResponse, AzulEntitiesResponse, AzulListParams, AzulSummaryResponse, @@ -48,17 +50,22 @@ export const fetchEntitiesFromQuery = async ( /** * Recursively call the endpoint to get a list of entities. This will iterate over the entity list until the next entity comes null * @param apiPath - Path that will be used to compose the API url + * @param accessToken - Access token. + * @param catalog - Catalog. + * @param listParams - Params to be used on the request. * @returns @see ListResponseType */ export const fetchAllEntities = async ( - apiPath: string + apiPath: string, + accessToken: string | undefined, + catalog?: string, + listParams?: AzulListParams ): Promise => { - const listParams = {}; const result = await fetchEntitiesFromQuery( apiPath, - listParams, - undefined, - undefined + listParams ?? {}, + catalog, + accessToken ); let hits = result.hits; let nextPage = result.pagination.next; @@ -72,6 +79,15 @@ export const fetchAllEntities = async ( return { ...result, hits } as AzulEntitiesResponse; }; +/** + * Fetch all catalogs and default catalog from given URL. + * @returns name of the default catalog and all available catalogs. + */ +export const fetchCatalog = async (): Promise => { + const res = await api().get(APIEndpoints.CATALOGS); + return res.data; +}; + /** * Request to get a single project. * @param id - entity's uuid. @@ -79,6 +95,7 @@ export const fetchAllEntities = async ( * @param catalog - Catalog. * @param accessToken - Access token. * @param defaultParams - Default parameters. + * @param swallow404 - Swallow 404 error. * @returns @see ProjectResponse */ export const fetchEntityDetail = async ( @@ -86,20 +103,32 @@ export const fetchEntityDetail = async ( apiPath: string, catalog: string | undefined, accessToken: string | undefined, - defaultParams = getDefaultDetailParams() + defaultParams = getDefaultDetailParams(), + swallow404 = false // eslint-disable-next-line @typescript-eslint/no-explicit-any -- this response type can't be determined beforehand ): Promise => { const catalogParam = catalog ? { [AZUL_PARAM.CATALOG]: catalog } : undefined; const options = getAxiosRequestOptions(accessToken); const baseURL = getEntityURL(); - const res = await api(baseURL).get( - `${apiPath}/${id}?${convertUrlParams({ - ...defaultParams, - ...catalogParam, - })}`, - options - ); - return res.data; + return await api(baseURL) + .get( + `${apiPath}/${id}?${convertUrlParams({ + ...defaultParams, + ...catalogParam, + })}`, + options + ) + .then((res) => { + return res.data; + }) + .catch((error) => { + if (swallow404) { + // skipping 404 error. + console.log(`Building stub page for ${id}.`); + } else { + throw error; + } + }); }; /** diff --git a/packages/data-explorer-ui/src/entity/common/client.ts b/packages/data-explorer-ui/src/entity/common/client.ts index 8ba50239..1e065b7a 100644 --- a/packages/data-explorer-ui/src/entity/common/client.ts +++ b/packages/data-explorer-ui/src/entity/common/client.ts @@ -24,9 +24,9 @@ export const configureInterceptors = (api: AxiosInstance): void => { return new Promise((resolve) => { setTimeout(() => resolve(api(config)), waitingTime); }); + } else { + return Promise.reject(error); } - - Promise.reject(error); } ); }; diff --git a/packages/data-explorer-ui/src/entity/service/model.ts b/packages/data-explorer-ui/src/entity/service/model.ts index 542b5361..6154a832 100644 --- a/packages/data-explorer-ui/src/entity/service/model.ts +++ b/packages/data-explorer-ui/src/entity/service/model.ts @@ -13,7 +13,9 @@ import { FilterState } from "../../hooks/useCategoryFilter"; export interface EntityService { fetchAllEntities: ( apiPath: string, - accessToken: string | undefined + accessToken: string | undefined, + catalog?: string, + listParams?: AzulListParams ) => Promise; fetchEntitiesFromQuery: ( @@ -40,9 +42,11 @@ export interface EntityService { apiPath: string, catalog: string | undefined, accessToken: string | undefined, - defaultParams?: + defaultParams: | DataSourceConfig["defaultDetailParams"] | DataSourceConfig["defaultParams"] + | undefined, + swallow404?: boolean // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This type can't be known before hand ) => Promise; diff --git a/packages/data-explorer-ui/src/hooks/useFetchEntity.tsx b/packages/data-explorer-ui/src/hooks/useFetchEntity.tsx index d889384e..3b93d552 100644 --- a/packages/data-explorer-ui/src/hooks/useFetchEntity.tsx +++ b/packages/data-explorer-ui/src/hooks/useFetchEntity.tsx @@ -48,7 +48,7 @@ export const useFetchEntity = ( useEffect(() => { // Fetch entity if entity data originates from a request, and has not yet been requested. if (shouldFetchEntity && uuid) { - run(fetchEntityDetail(uuid, path, catalog, token)); + run(fetchEntityDetail(uuid, path, catalog, token, undefined)); } }, [catalog, fetchEntityDetail, path, run, shouldFetchEntity, token, uuid]); diff --git a/packages/data-explorer-ui/src/hooks/useUpdateURLCatalogParam.ts b/packages/data-explorer-ui/src/hooks/useUpdateURLCatalogParam.ts new file mode 100644 index 00000000..e8e8ef2b --- /dev/null +++ b/packages/data-explorer-ui/src/hooks/useUpdateURLCatalogParam.ts @@ -0,0 +1,21 @@ +import Router, { useRouter } from "next/router"; +import { useEffect } from "react"; +import { useExploreState } from "./useExploreState"; + +/** + * Updates URL catalog params. + */ +export const useUpdateURLCatalogParams = (): void => { + const { exploreState } = useExploreState(); + const { basePath, pathname, query } = useRouter(); + const { catalogState } = exploreState; + + useEffect(() => { + if (!catalogState) return; + if ("catalog" in query && query.catalog === catalogState) return; + Router.replace({ + pathname: pathname?.replace(basePath, ""), + query: { ...query, catalog: catalogState }, + }); + }, [basePath, catalogState, pathname, query]); +}; diff --git a/packages/data-explorer-ui/src/views/EntityDetailView/entityDetailView.tsx b/packages/data-explorer-ui/src/views/EntityDetailView/entityDetailView.tsx index dac9ad8c..aa8a8d2e 100644 --- a/packages/data-explorer-ui/src/views/EntityDetailView/entityDetailView.tsx +++ b/packages/data-explorer-ui/src/views/EntityDetailView/entityDetailView.tsx @@ -10,6 +10,7 @@ import { useConfig } from "../../hooks/useConfig"; import { useCurrentDetailTab } from "../../hooks/useCurrentDetailTab"; import { useEntityHeadTitle } from "../../hooks/useEntityHeadTitle"; import { useFetchEntity } from "../../hooks/useFetchEntity"; +import { useUpdateURLCatalogParams } from "../../hooks/useUpdateURLCatalogParam"; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- this data type can't be determined beforehand export interface EntityDetailViewProps { @@ -34,6 +35,7 @@ function getTabs(entity: EntityConfig): Tab[] { } export const EntityDetailView = (props: EntityDetailViewProps): JSX.Element => { + useUpdateURLCatalogParams(); const { currentTab, route: tabRoute } = useCurrentDetailTab(); const { response } = useFetchEntity(props); const { push, query } = useRouter();