From c25d964fb3ecde86ac6d6753f6d788b90db471dc Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:54:48 +1000 Subject: [PATCH 1/6] fix: check if build speed can be improved (#1021) --- .gitignore | 6 +- .../Step/GTFStep/hooks/UseUCSCFiles/hook.ts | 7 +- .../hooks/UseRequirementsMatches/hook.ts | 5 +- .../hooks/UseRequirementsMatches/utils.ts | 7 +- .../hooks/UseENADataByTaxonomyId/hook.ts | 5 +- .../Stepper/components/Step/types.ts | 9 +- .../Main/components/Stepper/types.ts | 9 +- .../components/Main/types.ts | 9 +- .../components/SideColumn/types.ts | 9 +- .../components/Top/top.tsx | 2 +- .../components/Top/types.ts | 8 ++ .../components/Top/utils.ts | 4 +- app/services/workflows/entities.ts | 45 ++++++++++ .../workflows/hooks/UseEntities/hook.ts | 21 +++++ .../workflows/hooks/UseEntities/utils.ts | 20 +++++ app/services/workflows/loader.ts | 84 +++++++++++++++++++ app/services/workflows/query.ts | 35 ++++++++ app/services/workflows/routes.ts | 4 + app/services/workflows/store.ts | 47 +++++++++++ app/services/workflows/types.ts | 3 + app/views/WorkflowInputsView/types.ts | 11 +-- .../WorkflowInputsView/workflowInputsView.tsx | 20 +++-- package.json | 16 ++-- pages/_app.tsx | 8 +- .../[entityId]/[trsId]/index.tsx | 49 ++--------- scripts/sync-api-brc-analytics.sh | 16 ++++ scripts/sync-api-ga2.sh | 18 ++++ 27 files changed, 366 insertions(+), 111 deletions(-) create mode 100644 app/components/Entity/components/ConfigureWorkflowInputs/components/Top/types.ts create mode 100644 app/services/workflows/entities.ts create mode 100644 app/services/workflows/hooks/UseEntities/hook.ts create mode 100644 app/services/workflows/hooks/UseEntities/utils.ts create mode 100644 app/services/workflows/loader.ts create mode 100644 app/services/workflows/query.ts create mode 100644 app/services/workflows/routes.ts create mode 100644 app/services/workflows/store.ts create mode 100644 app/services/workflows/types.ts create mode 100755 scripts/sync-api-brc-analytics.sh create mode 100755 scripts/sync-api-ga2.sh diff --git a/.gitignore b/.gitignore index 74f6389e..cdf035a7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,12 @@ backend/**/.env # Playwright test artifacts /test-results /tests/screenshots +/playwright-report # python venv __pycache__ -playwright-report/ + + +## public api +/public/api diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/GTFStep/hooks/UseUCSCFiles/hook.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/GTFStep/hooks/UseUCSCFiles/hook.ts index 36bdd8aa..fd8182c5 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/GTFStep/hooks/UseUCSCFiles/hook.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/GTFStep/hooks/UseUCSCFiles/hook.ts @@ -1,17 +1,14 @@ import { useEffect, useState } from "react"; -import { BRCDataCatalogGenome } from "../../../../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; import { parseUCSCFilesResult } from "./utils"; import { UCSC_FILES_ENDPOINT } from "./constants"; import { UseUCSCFiles } from "./types"; -import { GA2AssemblyEntity } from "../../../../../../../../../../../../../apis/catalog/ga2/entities"; +import { Assembly } from "../../../../../../../../../../../../../views/WorkflowInputsView/types"; const SPECIAL_CASE_ASSEMBLY_LOOKUP: Record = { "GCF_000001405.40": "hg38", } as const; -export const useUCSCFiles = ( - genome: BRCDataCatalogGenome | GA2AssemblyEntity -): UseUCSCFiles => { +export const useUCSCFiles = (genome: Assembly): UseUCSCFiles => { const assemblyId = SPECIAL_CASE_ASSEMBLY_LOOKUP[genome.accession] ?? genome.accession; const [geneModelUrls, setGeneModelUrls] = useState(); diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/hook.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/hook.ts index a9a84a54..96ceff61 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/hook.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/hook.ts @@ -3,12 +3,11 @@ import { ReadRun } from "../../../../../../types"; import { UseRequirementsMatches } from "./types"; import { useMemo } from "react"; import { buildRequirementWarnings } from "./utils"; -import { BRCDataCatalogGenome } from "../../../../../../../../../../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; -import { GA2AssemblyEntity } from "../../../../../../../../../../../../../../../../../../../apis/catalog/ga2/entities"; +import { Assembly } from "../../../../../../../../../../../../../../../../../../../views/WorkflowInputsView/types"; export const useRequirementsMatches = ( table: Table, - genome: BRCDataCatalogGenome | GA2AssemblyEntity + genome: Assembly ): UseRequirementsMatches => { const { getSelectedRowModel, initialState } = table; const { columnFilters } = initialState; diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/utils.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/utils.ts index 67d2a38d..c59a92b0 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/utils.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/components/CollectionSummary/components/Alert/hooks/UseRequirementsMatches/utils.ts @@ -1,9 +1,8 @@ import { ColumnFiltersState, Row } from "@tanstack/react-table"; import { ReadRun } from "../../../../../../types"; import { COLUMN_KEY_TO_LABEL } from "./constants"; -import { BRCDataCatalogGenome } from "../../../../../../../../../../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; -import { GA2AssemblyEntity } from "../../../../../../../../../../../../../../../../../../../apis/catalog/ga2/entities"; import { LABEL } from "@databiosphere/findable-ui/lib/apis/azul/common/entities"; +import { Assembly } from "../../../../../../../../../../../../../../../../../../../views/WorkflowInputsView/types"; /** * Builds warnings for column filter mismatches. @@ -61,7 +60,7 @@ function buildDataWarnings( export function buildRequirementWarnings( initialColumnFilters: ColumnFiltersState, rows: Row[], - genome: BRCDataCatalogGenome | GA2AssemblyEntity + genome: Assembly ): string[] { if (rows.length === 0) return []; const speciesWarnings = buildSpeciesWarnings(rows, genome); @@ -77,7 +76,7 @@ export function buildRequirementWarnings( */ function buildSpeciesWarnings( rows: Row[], - genome: BRCDataCatalogGenome | GA2AssemblyEntity + genome: Assembly ): string[] { const { ncbiTaxonomyId, taxonomicLevelSpecies } = genome; diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/hooks/UseENADataByTaxonomyId/hook.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/hooks/UseENADataByTaxonomyId/hook.ts index 2ab8ad8b..758c8d0b 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/hooks/UseENADataByTaxonomyId/hook.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/SequencingStep/components/ENASequencingData/hooks/UseENADataByTaxonomyId/hook.ts @@ -3,13 +3,12 @@ import { UseENADataByTaxonomyId } from "./types"; import { fetchENAData } from "./request"; import { useAsync } from "@databiosphere/findable-ui/lib/hooks/useAsync"; import { isEligible } from "./utils"; -import { BRCDataCatalogGenome } from "../../../../../../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; -import { GA2AssemblyEntity } from "../../../../../../../../../../../../../../../apis/catalog/ga2/entities"; import { useConfig } from "@databiosphere/findable-ui/lib/hooks/useConfig"; import { AppSiteConfig } from "../../../../../../../../../../../../../../../../site-config/common/entities"; +import { Assembly } from "../../../../../../../../../../../../../../../views/WorkflowInputsView/types"; export const useENADataByTaxonomyId = ( - genome: BRCDataCatalogGenome | GA2AssemblyEntity + genome: Assembly ): UseENADataByTaxonomyId => { const { ncbiTaxonomyId: taxonomyId } = genome; const { config } = useConfig(); diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/types.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/types.ts index b6847183..5670478a 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/types.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/types.ts @@ -1,16 +1,13 @@ import { ComponentType, ReactNode } from "react"; import { StepProps as MStepProps } from "@mui/material"; -import { - BRCDataCatalogGenome, - Workflow, -} from "../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { Workflow } from "../../../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; import { ConfiguredInput, OnConfigure, } from "../../../../../../../../../../views/WorkflowInputsView/hooks/UseConfigureInputs/types"; import { Status, OnLaunchGalaxy } from "./hooks/UseLaunchGalaxy/types"; import { OnContinue, OnEdit } from "../../hooks/UseStepper/types"; -import { GA2AssemblyEntity } from "../../../../../../../../../../apis/catalog/ga2/entities"; +import { Assembly } from "../../../../../../../../../../views/WorkflowInputsView/types"; export interface StepConfig { description?: ReactNode; @@ -27,7 +24,7 @@ export interface StepProps Required> { configuredInput: ConfiguredInput; entryLabel: string; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; + genome: Assembly; onConfigure: OnConfigure; onContinue: OnContinue; onEdit: OnEdit; diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/types.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/types.ts index 135bff25..7e60f032 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/types.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/types.ts @@ -1,20 +1,17 @@ -import { - BRCDataCatalogGenome, - Workflow, -} from "../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { Workflow } from "../../../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; import { OnConfigure } from "../../../../../../../../views/WorkflowInputsView/hooks/UseConfigureInputs/types"; import { Status, OnLaunchGalaxy, } from "./components/Step/hooks/UseLaunchGalaxy/types"; -import { GA2AssemblyEntity } from "../../../../../../../../apis/catalog/ga2/entities"; import { StepConfig } from "./components/Step/types"; import { ConfiguredInput } from "../../../../../../../../views/WorkflowInputsView/hooks/UseConfigureInputs/types"; +import { Assembly } from "../../../../../../../../views/WorkflowInputsView/types"; export interface Props { configuredInput: ConfiguredInput; configuredSteps: StepConfig[]; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; + genome: Assembly; onConfigure: OnConfigure; onLaunchGalaxy: OnLaunchGalaxy; status: Status; diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/types.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/types.ts index 16a5d2af..b0cbd4be 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/types.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Main/types.ts @@ -1,18 +1,15 @@ -import { - BRCDataCatalogGenome, - Workflow, -} from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { Workflow } from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; import { ConfiguredInput, OnConfigure, } from "../../../../../../views/WorkflowInputsView/hooks/UseConfigureInputs/types"; -import { GA2AssemblyEntity } from "../../../../../../apis/catalog/ga2/entities"; import { StepConfig } from "./components/Stepper/components/Step/types"; +import { Assembly } from "../../../../../../views/WorkflowInputsView/types"; export interface Props { configuredInput: ConfiguredInput; configuredSteps: StepConfig[]; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; + genome: Assembly; onConfigure: OnConfigure; workflow: Workflow; } diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/types.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/types.ts index 9e43047f..ac569379 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/types.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/types.ts @@ -1,14 +1,11 @@ -import { - BRCDataCatalogGenome, - Workflow, -} from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { Workflow } from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; import { ConfiguredInput } from "../../../../../../views/WorkflowInputsView/hooks/UseConfigureInputs/types"; -import { GA2AssemblyEntity } from "../../../../../../apis/catalog/ga2/entities"; import { StepConfig } from "../../../../../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/components/Step/types"; +import { Assembly } from "../../../../../../views/WorkflowInputsView/types"; export interface Props { configuredInput: ConfiguredInput; configuredSteps: StepConfig[]; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; + genome: Assembly; workflow: Workflow; } diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/top.tsx b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/top.tsx index b2f6fe0a..f349665f 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/top.tsx +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/top.tsx @@ -1,4 +1,4 @@ -import { Props } from "../../../../../../views/WorkflowInputsView/types"; +import { Props } from "./types"; import { getBreadcrumbs } from "./utils"; import { BackPageHero } from "@databiosphere/findable-ui/lib/components/Layout/components/BackPage/components/BackPageHero/backPageHero"; diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/types.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/types.ts new file mode 100644 index 00000000..d339858d --- /dev/null +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/types.ts @@ -0,0 +1,8 @@ +import { Workflow } from "../../../../../../apis/catalog/brc-analytics-catalog/common/entities"; +import { Assembly } from "../../../../../../views/WorkflowInputsView/types"; + +export interface Props { + entityId: string; + genome: Assembly; + workflow: Workflow; +} diff --git a/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/utils.ts b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/utils.ts index af832bc5..9f90e990 100644 --- a/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/utils.ts +++ b/app/components/Entity/components/ConfigureWorkflowInputs/components/Top/utils.ts @@ -1,7 +1,7 @@ -import { ROUTES } from "routes/constants"; -import { Props } from "../../../../../../views/WorkflowInputsView/types"; +import { ROUTES } from "../../../../../../../routes/constants"; import { Breadcrumb } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; import { replaceParameters } from "@databiosphere/findable-ui/lib/utils/replaceParameters"; +import { Props } from "./types"; /** * Returns breadcrumbs for the workflow input view. diff --git a/app/services/workflows/entities.ts b/app/services/workflows/entities.ts new file mode 100644 index 00000000..c237c7f4 --- /dev/null +++ b/app/services/workflows/entities.ts @@ -0,0 +1,45 @@ +import { + BRCDataCatalogGenome, + Workflow, + WorkflowCategory, +} from "../../apis/catalog/brc-analytics-catalog/common/entities"; +import { getEntities, getEntity } from "./query"; +import { GA2AssemblyEntity } from "../../apis/catalog/ga2/entities"; + +/** + * Gets assemblies. + * @returns Assemblies. + */ +export function getAssemblies< + T extends BRCDataCatalogGenome | GA2AssemblyEntity, +>(): T[] { + return getEntities("assemblies"); +} + +/** + * Gets assembly by entity id. + * @param entityId - Entity id. + * @returns Assembly. + */ +export function getAssembly( + entityId: string +): T { + return getEntity("assemblies", entityId); +} + +/** + * Gets workflow by TRS id. + * @param trsId - TRS id. + * @returns Workflow. + */ +export function getWorkflow(trsId: string): Workflow { + return getEntity("workflows", trsId); +} + +/** + * Gets workflows. + * @returns Workflows. + */ +export function getWorkflows(): WorkflowCategory[] { + return getEntities("workflows"); +} diff --git a/app/services/workflows/hooks/UseEntities/hook.ts b/app/services/workflows/hooks/UseEntities/hook.ts new file mode 100644 index 00000000..dc15b249 --- /dev/null +++ b/app/services/workflows/hooks/UseEntities/hook.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; +import { ensureEntitiesLoaded } from "./utils"; +import { getConfig } from "@databiosphere/findable-ui/lib/config/config"; + +export function useEntities(): boolean { + const [isLoaded, setIsLoaded] = useState(false); + + const config = getConfig(); + + useEffect(() => { + if (!config) return; + + ensureEntitiesLoaded(config) + .then(() => setIsLoaded(true)) + .catch((err) => { + throw new Error("Failed to load entities", err); + }); + }, [config]); + + return isLoaded; +} diff --git a/app/services/workflows/hooks/UseEntities/utils.ts b/app/services/workflows/hooks/UseEntities/utils.ts new file mode 100644 index 00000000..ca9096c4 --- /dev/null +++ b/app/services/workflows/hooks/UseEntities/utils.ts @@ -0,0 +1,20 @@ +import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { loadEntities, loadWorkflows } from "../../loader"; + +let loadPromise: Promise | null = null; + +/** + * Ensures that the entities and workflows are loaded. + * @param config - Site config. + * @returns Promise that resolves when the entities and workflows are loaded. + */ +export function ensureEntitiesLoaded(config: SiteConfig): Promise { + if (loadPromise) return loadPromise; + + loadPromise = (async (): Promise => { + await loadWorkflows(); + await loadEntities(config); + })(); + + return loadPromise; +} diff --git a/app/services/workflows/loader.ts b/app/services/workflows/loader.ts new file mode 100644 index 00000000..a32dc64a --- /dev/null +++ b/app/services/workflows/loader.ts @@ -0,0 +1,84 @@ +import { API } from "./routes"; +import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities"; +import { getEntitiesById, setEntitiesById, setEntitiesByType } from "./store"; +import { EntityRoute } from "./types"; +import { + Workflow, + WorkflowCategory, +} from "../../apis/catalog/brc-analytics-catalog/common/entities"; +import { formatTrsId } from "../../components/Entity/components/AnalysisMethodsCatalog/utils"; +import { CUSTOM_WORKFLOW } from "../../components/Entity/components/AnalysisMethod/components/CustomWorkflow/constants"; + +/** + * Fetches entities from the API. + * @param url - URL. + * @returns Entity list. + */ +async function fetchEntities(url: string): Promise { + const res = await fetch(url); + + if (!res.ok) throw new Error(`Failed to fetch: ${url}`); + + return (await res.json()) as unknown[]; +} + +/** + * Checks if the route is an entity route. + * @param route - Route. + * @returns True if the route is an entity route; false otherwise. + */ +function isEntityRoute(route: string): route is EntityRoute { + return route in API; +} + +/** + * Loads the entities store with entities from the API. + * @param config - Site config. + */ +export async function loadEntities(config: SiteConfig): Promise { + for (const entity of config.entities) { + const { getId, route } = entity; + + if (!isEntityRoute(route)) continue; + + const apiRoute = API[route]; + + // Entities are already loaded; skip. + if (getEntitiesById().has(route)) continue; + + // Get id function is not configured; entities are excluded from preloading. + if (!getId) continue; + + // Fetch the entities. + const entities = await fetchEntities(apiRoute); + + const entityById = new Map(); + for (const entity of entities) entityById.set(getId(entity), entity); + + setEntitiesById(route, entityById); + setEntitiesByType(route, entities); + } +} + +/** + * Loads the workflows store with workflows from the API. + */ +export async function loadWorkflows(): Promise { + const workflowCategories = (await fetchEntities( + API.workflows + )) as WorkflowCategory[]; + + const workflows = workflowCategories.flatMap((w) => w.workflows); + + const workflowById = new Map(); + + for (const workflow of workflows) { + workflowById.set(formatTrsId(workflow.trsId), workflow); + } + + // Add custom workflow. + workflowById.set(CUSTOM_WORKFLOW.trsId, CUSTOM_WORKFLOW); + + setEntitiesById("workflows", workflowById); + setEntitiesByType("workflows", workflowCategories); +} diff --git a/app/services/workflows/query.ts b/app/services/workflows/query.ts new file mode 100644 index 00000000..ff593e9f --- /dev/null +++ b/app/services/workflows/query.ts @@ -0,0 +1,35 @@ +import { getEntitiesById, getEntitiesByType } from "./store"; +import { EntityRoute } from "./types"; + +/** + * Gets entities by entity list type. + * @param entityListType - Entity list type. + * @returns Map of entity list types to entities. + */ +export function getEntities(entityListType: EntityRoute): T[] { + const entities = getEntitiesByType().get(entityListType); + + if (!entities) + throw new Error( + `No entities found for entity list type: ${entityListType}` + ); + + return entities as T[]; +} + +/** + * Gets entity by entity list type and entity id. + * @param entityListType - Entity list type. + * @param entityId - Entity id. + * @returns Entity. + */ +export function getEntity(entityListType: EntityRoute, entityId: string): T { + const entity = getEntitiesById().get(entityListType)?.get(entityId); + + if (!entity) + throw new Error( + `No entity found for entity list type: ${entityListType} and entity id: ${entityId}` + ); + + return entity as T; +} diff --git a/app/services/workflows/routes.ts b/app/services/workflows/routes.ts new file mode 100644 index 00000000..9882fc2a --- /dev/null +++ b/app/services/workflows/routes.ts @@ -0,0 +1,4 @@ +export const API = { + assemblies: "/api/assemblies.json", + workflows: "/api/workflows.json", +} as const; diff --git a/app/services/workflows/store.ts b/app/services/workflows/store.ts new file mode 100644 index 00000000..0bf43ff5 --- /dev/null +++ b/app/services/workflows/store.ts @@ -0,0 +1,47 @@ +import { EntityRoute } from "./types"; + +// entityListType -> T[] +const ENTITIES_BY_TYPE = new Map(); + +// entityListType -> entityId -> T +const ENTITIES_BY_ENTITY_ID = new Map>(); + +/** + * Gets entities by entity id. + * @returns Map of entity list types to entity id to entity. + */ +export function getEntitiesById(): Map> { + return ENTITIES_BY_ENTITY_ID as Map>; +} + +/** + * Gets entities by entity list type. + * @returns Map of entity list types to entities. + */ +export function getEntitiesByType(): Map { + return ENTITIES_BY_TYPE as Map; +} + +/** + * Sets entities by entity id. + * @param entityListType - Entity list type. + * @param entitiesById - Map of entity id to entity. + */ +export function setEntitiesById( + entityListType: EntityRoute, + entitiesById: Map +): void { + ENTITIES_BY_ENTITY_ID.set(entityListType, entitiesById); +} + +/** + * Sets entities by entity list type. + * @param entityListType - Entity list type. + * @param entities - Entities. + */ +export function setEntitiesByType( + entityListType: EntityRoute, + entities: unknown[] +): void { + ENTITIES_BY_TYPE.set(entityListType, entities); +} diff --git a/app/services/workflows/types.ts b/app/services/workflows/types.ts new file mode 100644 index 00000000..a5af2917 --- /dev/null +++ b/app/services/workflows/types.ts @@ -0,0 +1,3 @@ +import { API } from "./routes"; + +export type EntityRoute = keyof typeof API; diff --git a/app/views/WorkflowInputsView/types.ts b/app/views/WorkflowInputsView/types.ts index 48bbe40d..7f7deba5 100644 --- a/app/views/WorkflowInputsView/types.ts +++ b/app/views/WorkflowInputsView/types.ts @@ -1,11 +1,4 @@ -import { - BRCDataCatalogGenome, - Workflow, -} from "../../apis/catalog/brc-analytics-catalog/common/entities"; +import { BRCDataCatalogGenome } from "../../apis/catalog/brc-analytics-catalog/common/entities"; import { GA2AssemblyEntity } from "../../apis/catalog/ga2/entities"; -export interface Props { - entityId: string; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; - workflow: Workflow; -} +export type Assembly = BRCDataCatalogGenome | GA2AssemblyEntity; diff --git a/app/views/WorkflowInputsView/workflowInputsView.tsx b/app/views/WorkflowInputsView/workflowInputsView.tsx index 6a21bfb0..c0ecb750 100644 --- a/app/views/WorkflowInputsView/workflowInputsView.tsx +++ b/app/views/WorkflowInputsView/workflowInputsView.tsx @@ -1,5 +1,5 @@ import { Detail } from "@databiosphere/findable-ui/lib/components/Detail/detail"; -import { Props } from "./types"; +import { Assembly } from "./types"; import { Top } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Top/top"; import { SideColumn } from "../../components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/sideColumn"; import { Main } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/main"; @@ -7,19 +7,26 @@ import { useConfigureInputs } from "./hooks/UseConfigureInputs/useConfigureInput import { useConfiguredSteps } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/hook"; import { augmentConfiguredSteps } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/utils"; import { SEQUENCING_STEPS } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/constants"; +import { Props } from "../../../pages/data/[entityListType]/[entityId]/[trsId]"; +import { getAssembly } from "../../services/workflows/entities"; +import { getWorkflow } from "../../services/workflows/entities"; + +export const WorkflowInputsView = ({ entityId, trsId }: Props): JSX.Element => { + const genome = getAssembly(entityId); + const workflow = getWorkflow(trsId); -export const WorkflowInputsView = (props: Props): JSX.Element => { - const { workflow } = props; const { configuredInput, onConfigure } = useConfigureInputs(); const { configuredSteps } = useConfiguredSteps(workflow); + return ( } sideColumn={ @@ -30,10 +37,11 @@ export const WorkflowInputsView = (props: Props): JSX.Element => { configuredInput, SEQUENCING_STEPS )} - {...props} + genome={genome} + workflow={workflow} /> } - top={} + top={} /> ); }; diff --git a/package.json b/package.json index 120d5521..d2bfc9bd 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,14 @@ "version": "0.18.0", "private": true, "scripts": { - "dev": "./scripts/dev.sh brc-analytics && ./scripts/set-version.sh dev && next dev", - "build:local": "./scripts/build.sh brc-analytics local && ./scripts/set-version.sh && next build", - "build:dev": "./scripts/build.sh brc-analytics dev && ./scripts/set-version.sh && next build", - "build:prod": "./scripts/build.sh brc-analytics prod && ./scripts/set-version.sh && next build", - "dev:ga2": "./scripts/dev.sh ga2 && ./scripts/set-version.sh dev && next dev", - "build-local:ga2": "./scripts/build.sh ga2 local && ./scripts/set-version.sh && next build", - "build-dev:ga2": "./scripts/build.sh ga2 dev && ./scripts/set-version.sh && next build", - "build-prod:ga2": "./scripts/build.sh ga2 prod && ./scripts/set-version.sh && next build", + "dev": "./scripts/dev.sh brc-analytics && ./scripts/set-version.sh dev && ./scripts/sync-api-brc-analytics.sh && next dev", + "build:local": "./scripts/build.sh brc-analytics local && ./scripts/set-version.sh && ./scripts/sync-api-brc-analytics.sh && next build", + "build:dev": "./scripts/build.sh brc-analytics dev && ./scripts/set-version.sh && ./scripts/sync-api-brc-analytics.sh && next build", + "build:prod": "./scripts/build.sh brc-analytics prod && ./scripts/set-version.sh && ./scripts/sync-api-brc-analytics.sh && next build", + "dev:ga2": "./scripts/dev.sh ga2 && ./scripts/set-version.sh dev && ./scripts/sync-api-ga2.sh && next dev", + "build-local:ga2": "./scripts/build.sh ga2 local && ./scripts/set-version.sh && ./scripts/sync-api-ga2.sh && next build", + "build-dev:ga2": "./scripts/build.sh ga2 dev && ./scripts/set-version.sh && ./scripts/sync-api-ga2.sh && next build", + "build-prod:ga2": "./scripts/build.sh ga2 prod && ./scripts/set-version.sh && ./scripts/sync-api-ga2.sh && next build", "start": "npx serve out", "lint": "next lint --dir .", "check-format": "prettier --check .", diff --git a/pages/_app.tsx b/pages/_app.tsx index 03307bd6..87aac0f4 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -21,9 +21,10 @@ import { config } from "../app/config/config"; import { mergeAppTheme } from "../app/theme/theme"; import { GoogleSignInAuthenticationProvider } from "@databiosphere/findable-ui/lib/providers/googleSignInAuthentication/provider"; import { ServicesProvider } from "@databiosphere/findable-ui/lib/providers/services/provider"; +import "../app/styles/fonts/fonts.css"; +import { useEntities } from "../app/services/workflows/hooks/UseEntities/hook"; const DEFAULT_ENTITY_LIST_TYPE = "organisms"; -import "../app/styles/fonts/fonts.css"; export interface PageProps extends AzulEntitiesStaticResponse { pageTitle?: string; @@ -42,6 +43,8 @@ export type AppPropsWithComponent = AppProps & { function MyApp({ Component, pageProps }: AppPropsWithComponent): JSX.Element { // Set up the site configuration, layout and theme. const appConfig = config(); + // Load entities into the in-memory cache. + const isEntitiesLoaded = useEntities(); const { layout, redirectRootToPath, @@ -56,6 +59,9 @@ function MyApp({ Component, pageProps }: AppPropsWithComponent): JSX.Element { const appTheme = mergeAppTheme(baseThemeOptions, themeOptions); const AppLayout = Component.AppLayout || DXAppLayout; const Main = Component.Main || DXMain; + + if (!isEntitiesLoaded) return <>; + return ( diff --git a/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx b/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx index b9c695c8..a2aacd8c 100644 --- a/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx +++ b/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx @@ -6,11 +6,10 @@ import { import { ParsedUrlQuery } from "querystring"; import { config } from "../../../../../app/config/config"; import { seedDatabase } from "../../../../../app/utils/seedDatabase"; -import { getEntities, getEntity } from "../../../../../app/utils/entityUtils"; +import { getEntities } from "../../../../../app/utils/entityUtils"; import { BRCDataCatalogGenome, EntitiesResponse, - Workflow, } from "../../../../../app/apis/catalog/brc-analytics-catalog/common/entities"; import { EntityConfig } from "@databiosphere/findable-ui/lib/config/entities"; import workflows from "../../../../../catalog/output/workflows.json"; @@ -18,7 +17,6 @@ import { formatTrsId, workflowIsCompatibleWithAssembly, } from "../../../../../app/components/Entity/components/AnalysisMethodsCatalog/utils"; -import { getEntityConfig } from "@databiosphere/findable-ui/lib/config/utils"; import { WorkflowInputsView } from "../../../../../app/views/WorkflowInputsView/workflowInputsView"; import { GA2AssemblyEntity } from "../../../../../app/apis/catalog/ga2/entities"; import { CUSTOM_WORKFLOW } from "../../../../../app/components/Entity/components/AnalysisMethod/components/CustomWorkflow/constants"; @@ -33,10 +31,10 @@ interface PageUrlParams extends ParsedUrlQuery { trsId: string; } -interface Props { +export interface Props { entityId: string; - genome: BRCDataCatalogGenome | GA2AssemblyEntity; - workflow: Workflow; + entityListType: string; + trsId: string; } export const getStaticPaths: GetStaticPaths = async () => { @@ -64,49 +62,12 @@ export const getStaticPaths: GetStaticPaths = async () => { export const getStaticProps = async ( context: GetStaticPropsContext ): Promise> => { - const appConfig = config(); - const { entities } = appConfig; const { entityId, entityListType, trsId } = context.params as PageUrlParams; if (!entityListType || !entityId || !trsId) return { notFound: true }; if (entityListType !== "assemblies") return { notFound: true }; - const entityConfig = getEntityConfig(entities, entityListType); - - if (!entityConfig) return { notFound: true }; - - // Seed database. - await seedDatabase(entityConfig.route, entityConfig); - const genome = await getEntity( - entityConfig, - entityId - ); - - // Find workflow. - const workflow = workflows - .flatMap((w) => w.workflows) - .find((workflow) => formatTrsId(workflow.trsId) === trsId); - - // Custom workflow. - if (trsId === CUSTOM_WORKFLOW.trsId) { - return { - props: { - entityId, - genome, - workflow: CUSTOM_WORKFLOW, - }, - }; - } - - if (!workflow) return { notFound: true }; - - return { - props: { - entityId, - genome, - workflow, - }, - }; + return { props: { entityId, entityListType, trsId } }; }; const ConfigureWorkflowInputs = (props: Props): JSX.Element => { diff --git a/scripts/sync-api-brc-analytics.sh b/scripts/sync-api-brc-analytics.sh new file mode 100755 index 00000000..b9bd6f54 --- /dev/null +++ b/scripts/sync-api-brc-analytics.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +# JSON source root +SRC_ROOT="catalog/output" + +API_DIR="public/api" +mkdir -p "$API_DIR" + +PER_APP_JSONS=("assemblies" "workflows") + +for name in "${PER_APP_JSONS[@]}"; do + src="$SRC_ROOT/${name}.json" + dst="$API_DIR/${name}.json" + cp "$src" "$dst" +done diff --git a/scripts/sync-api-ga2.sh b/scripts/sync-api-ga2.sh new file mode 100755 index 00000000..bde2369c --- /dev/null +++ b/scripts/sync-api-ga2.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +# JSON source root +SRC_ROOT="catalog/ga2/output" + +API_DIR="public/api" +mkdir -p "$API_DIR" + +PER_APP_JSONS=("assemblies") + +for name in "${PER_APP_JSONS[@]}"; do + src="$SRC_ROOT/${name}.json" + dst="$API_DIR/${name}.json" + cp "$src" "$dst" +done + +cp "catalog/output/workflows.json" "$API_DIR/workflows.json" From df3957de7d04f1edcd6f28ce085e8078bcded40c Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:24:13 +1000 Subject: [PATCH 2/6] feat: added tests for workflows service (#1021) --- tests/services/workflows.loader.test.ts | 158 ++++++++++++++++++++++++ tests/services/workflows.query.test.ts | 48 +++++++ tests/services/workflows.store.test.ts | 52 ++++++++ 3 files changed, 258 insertions(+) create mode 100644 tests/services/workflows.loader.test.ts create mode 100644 tests/services/workflows.query.test.ts create mode 100644 tests/services/workflows.store.test.ts diff --git a/tests/services/workflows.loader.test.ts b/tests/services/workflows.loader.test.ts new file mode 100644 index 00000000..199d6592 --- /dev/null +++ b/tests/services/workflows.loader.test.ts @@ -0,0 +1,158 @@ +import { API } from "../../app/services/workflows/routes"; +import { + getEntitiesById, + getEntitiesByType, +} from "../../app/services/workflows/store"; +import { + loadEntities, + loadWorkflows, +} from "../../app/services/workflows/loader"; +import { SiteConfig } from "@databiosphere/findable-ui/lib/config/entities"; + +const CONFIG = { + entities: [{ getId, route: "assemblies" }], +} as SiteConfig; + +jest.mock( + "../../app/components/Entity/components/AnalysisMethodsCatalog/utils", + () => ({ formatTrsId: (trsId: string): string => trsId }) +); + +jest.mock( + "../../app/components/Entity/components/AnalysisMethod/components/CustomWorkflow/constants", + () => ({ CUSTOM_WORKFLOW: { trsId: "custom-workflow" } }) +); + +describe("workflows loader", () => { + let fetchMock: jest.MockedFunction; + + beforeEach(() => { + getEntitiesById().clear(); + getEntitiesByType().clear(); + fetchMock = jest.fn(); + global.fetch = fetchMock; + }); + + describe("loadEntities", () => { + test("skips routes not in API", async () => { + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse()); + + await loadEntities({ + entities: [{ getId, route: "not-a-valid-route" }], + } as SiteConfig); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(getEntitiesById().size).toBe(0); + expect(getEntitiesByType().size).toBe(0); + }); + + test("skips entities when getId is not configured", async () => { + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse()); + + await loadEntities({ entities: [{ route: "assemblies" }] } as SiteConfig); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(getEntitiesById().has("assemblies")).toBe(false); + }); + + test("skips routes that are already loaded", async () => { + const existing = new Map(); + existing.set("asm1", { id: "asm1" }); + getEntitiesById().set("assemblies", existing); + + const payload = [{ id: "asm2" }]; + + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse(payload)); + + await loadEntities(CONFIG); + + // Should not re-fetch or overwrite existing entries. + expect(fetchMock).not.toHaveBeenCalled(); + + const byId = getEntitiesById().get("assemblies"); + expect(byId?.get("asm1")).toEqual({ id: "asm1" }); + expect(byId?.get("asm2")).toBeUndefined(); + }); + + test("loads entities from API and populates store", async () => { + const payload = [{ id: "asm1" }, { id: "asm2" }]; + + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse(payload)); + + await loadEntities(CONFIG); + + expect(fetchMock).toHaveBeenCalledWith(API.assemblies); + + const byId = getEntitiesById().get("assemblies"); + const byType = getEntitiesByType().get("assemblies"); + + expect(byType).toEqual(payload); + expect(byId?.get("asm1")).toEqual({ id: "asm1" }); + expect(byId?.get("asm2")).toEqual({ id: "asm2" }); + }); + + test("throws when fetch fails", async () => { + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse([], false)); + + await expect(loadEntities(CONFIG)).rejects.toThrow( + `Failed to fetch: ${API.assemblies}` + ); + }); + }); + + describe("loadWorkflows", () => { + test("loads workflow categories, flattens workflows, and populates store", async () => { + const categories = [{ workflows: [{ trsId: "trs-1" }] }]; + + fetchMock.mockResolvedValue(mockFetchResponse(categories)); + + await loadWorkflows(); + + expect(fetchMock).toHaveBeenCalledWith(API.workflows); + + const byId = getEntitiesById().get("workflows"); + const byType = getEntitiesByType().get("workflows"); + + expect(byType).toEqual(categories); + + expect(byId?.has("trs-1")).toBe(true); + expect(byId?.has("custom-workflow")).toBe(true); + }); + + test("throws when workflow fetch fails", async () => { + const fetchMock = global.fetch as jest.Mock; + fetchMock.mockResolvedValue(mockFetchResponse([], false)); + + await expect(loadWorkflows()).rejects.toThrow( + `Failed to fetch: ${API.workflows}` + ); + }); + }); +}); + +/** + * Mocks the getId function. + * @param e - Entity. + * @returns Entity id. + */ +function getId(e: T): string { + return e.id; +} + +/** + * Mocks the fetch response. + * @param payload - Payload. + * @param ok - OK. + * @returns Fetch response. + */ +function mockFetchResponse( + payload: unknown[] = [{ id: "1" }], + ok = true +): Response { + return { json: async () => payload, ok } as Response; +} diff --git a/tests/services/workflows.query.test.ts b/tests/services/workflows.query.test.ts new file mode 100644 index 00000000..dd84202d --- /dev/null +++ b/tests/services/workflows.query.test.ts @@ -0,0 +1,48 @@ +import { + getEntitiesById, + getEntitiesByType, + setEntitiesById, + setEntitiesByType, +} from "../../app/services/workflows/store"; +import { getEntities, getEntity } from "../../app/services/workflows/query"; + +describe("workflows query", () => { + beforeEach(() => { + getEntitiesById().clear(); + getEntitiesByType().clear(); + }); + + test("getEntities returns entities for given list type", () => { + const assemblies = [{ id: "asm1" }, { id: "asm2" }]; + + setEntitiesByType("assemblies", assemblies); + + expect(getEntities("assemblies")).toEqual(assemblies); + }); + + test("getEntities throws when no entities exist for list type", () => { + expect(() => getEntities("assemblies")).toThrow( + "No entities found for entity list type: assemblies" + ); + }); + + test("getEntity returns entity by id for given list type", () => { + const assembliesMap = new Map(); + assembliesMap.set("asm1", { id: "asm1" }); + + setEntitiesById("assemblies", assembliesMap); + + expect(getEntity("assemblies", "asm1")).toEqual({ id: "asm1" }); + }); + + test("getEntity throws when entity is not found", () => { + const assembliesMap = new Map(); + assembliesMap.set("asm1", { id: "asm1" }); + + setEntitiesById("assemblies", assembliesMap); + + expect(() => getEntity("assemblies", "missing")).toThrow( + "No entity found for entity list type: assemblies and entity id: missing" + ); + }); +}); diff --git a/tests/services/workflows.store.test.ts b/tests/services/workflows.store.test.ts new file mode 100644 index 00000000..1a753580 --- /dev/null +++ b/tests/services/workflows.store.test.ts @@ -0,0 +1,52 @@ +import { + getEntitiesById, + getEntitiesByType, + setEntitiesById, + setEntitiesByType, +} from "../../app/services/workflows/store"; + +describe("workflows store", () => { + beforeEach(() => { + getEntitiesById().clear(); + getEntitiesByType().clear(); + }); + + test("setEntitiesById and getEntitiesById store entities keyed by route", () => { + const assemblies = new Map(); + assemblies.set("asm1", { id: "asm1" }); + assemblies.set("asm2", { id: "asm2" }); + + setEntitiesById("assemblies", assemblies); + + const byId = getEntitiesById(); + + expect(byId.get("assemblies")?.get("asm1")).toEqual({ id: "asm1" }); + expect(byId.get("assemblies")?.get("asm2")).toEqual({ id: "asm2" }); + }); + + test("setEntitiesByType and getEntitiesByType store entity arrays keyed by route", () => { + const assemblies = [{ id: "asm1" }, { id: "asm2" }]; + + setEntitiesByType("assemblies", assemblies); + + const byType = getEntitiesByType(); + + expect(byType.get("assemblies")).toEqual(assemblies); + }); + + test("stores maintain independent entries for different routes", () => { + const assemblies = new Map(); + assemblies.set("asm1", { id: "asm1" }); + + const workflows = new Map(); + workflows.set("wf1", { id: "wf1" }); + + setEntitiesById("assemblies", assemblies); + setEntitiesById("workflows", workflows); + + const byId = getEntitiesById(); + + expect(byId.get("assemblies")?.get("asm1")).toEqual({ id: "asm1" }); + expect(byId.get("workflows")?.get("wf1")).toEqual({ id: "wf1" }); + }); +}); From 4a7870012c7c709e35d2bde39fec4d6761e7f1cd Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:32:11 +1000 Subject: [PATCH 3/6] refactor: error (#1021) --- app/services/workflows/hooks/UseEntities/hook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/workflows/hooks/UseEntities/hook.ts b/app/services/workflows/hooks/UseEntities/hook.ts index dc15b249..44bd5260 100644 --- a/app/services/workflows/hooks/UseEntities/hook.ts +++ b/app/services/workflows/hooks/UseEntities/hook.ts @@ -13,7 +13,7 @@ export function useEntities(): boolean { ensureEntitiesLoaded(config) .then(() => setIsLoaded(true)) .catch((err) => { - throw new Error("Failed to load entities", err); + throw new Error(`Failed to load entities: ${err}`); }); }, [config]); From 309e4fd483cbcdb58420dfbe49a64df56af5a9e3 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:33:38 +1000 Subject: [PATCH 4/6] fix: early exit on loading workflows (#1021) --- app/services/workflows/loader.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/services/workflows/loader.ts b/app/services/workflows/loader.ts index a32dc64a..4df53782 100644 --- a/app/services/workflows/loader.ts +++ b/app/services/workflows/loader.ts @@ -64,6 +64,8 @@ export async function loadEntities(config: SiteConfig): Promise { * Loads the workflows store with workflows from the API. */ export async function loadWorkflows(): Promise { + if (getEntitiesById().has("workflows")) return; + const workflowCategories = (await fetchEntities( API.workflows )) as WorkflowCategory[]; From a41ffad2d1c8bb76b6d447828f4c87f7137dae13 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:40:17 +1000 Subject: [PATCH 5/6] fix: clear json from public api folder prior to copying new json (#1021) --- scripts/sync-api-brc-analytics.sh | 2 ++ scripts/sync-api-ga2.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/sync-api-brc-analytics.sh b/scripts/sync-api-brc-analytics.sh index b9bd6f54..85064914 100755 --- a/scripts/sync-api-brc-analytics.sh +++ b/scripts/sync-api-brc-analytics.sh @@ -9,6 +9,8 @@ mkdir -p "$API_DIR" PER_APP_JSONS=("assemblies" "workflows") +rm -f "$API_DIR"/*.json + for name in "${PER_APP_JSONS[@]}"; do src="$SRC_ROOT/${name}.json" dst="$API_DIR/${name}.json" diff --git a/scripts/sync-api-ga2.sh b/scripts/sync-api-ga2.sh index bde2369c..fef74d19 100755 --- a/scripts/sync-api-ga2.sh +++ b/scripts/sync-api-ga2.sh @@ -9,6 +9,8 @@ mkdir -p "$API_DIR" PER_APP_JSONS=("assemblies") +rm -f "$API_DIR"/*.json + for name in "${PER_APP_JSONS[@]}"; do src="$SRC_ROOT/${name}.json" dst="$API_DIR/${name}.json" From 5b6d4b41dbd5592bffca878a942a91d6d81e80d7 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:55:11 +1000 Subject: [PATCH 6/6] fix: reuse of page props in workflow inputs view (#1021) --- app/views/WorkflowInputsView/types.ts | 6 ++++++ app/views/WorkflowInputsView/workflowInputsView.tsx | 3 +-- pages/data/[entityListType]/[entityId]/[trsId]/index.tsx | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/WorkflowInputsView/types.ts b/app/views/WorkflowInputsView/types.ts index 7f7deba5..e50a6dd1 100644 --- a/app/views/WorkflowInputsView/types.ts +++ b/app/views/WorkflowInputsView/types.ts @@ -2,3 +2,9 @@ import { BRCDataCatalogGenome } from "../../apis/catalog/brc-analytics-catalog/c import { GA2AssemblyEntity } from "../../apis/catalog/ga2/entities"; export type Assembly = BRCDataCatalogGenome | GA2AssemblyEntity; + +export interface Props { + entityId: string; + entityListType: string; + trsId: string; +} diff --git a/app/views/WorkflowInputsView/workflowInputsView.tsx b/app/views/WorkflowInputsView/workflowInputsView.tsx index c0ecb750..f680f452 100644 --- a/app/views/WorkflowInputsView/workflowInputsView.tsx +++ b/app/views/WorkflowInputsView/workflowInputsView.tsx @@ -1,5 +1,5 @@ import { Detail } from "@databiosphere/findable-ui/lib/components/Detail/detail"; -import { Assembly } from "./types"; +import { Assembly, Props } from "./types"; import { Top } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Top/top"; import { SideColumn } from "../../components/Entity/components/ConfigureWorkflowInputs/components/SideColumn/sideColumn"; import { Main } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/main"; @@ -7,7 +7,6 @@ import { useConfigureInputs } from "./hooks/UseConfigureInputs/useConfigureInput import { useConfiguredSteps } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/hook"; import { augmentConfiguredSteps } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/utils"; import { SEQUENCING_STEPS } from "../../components/Entity/components/ConfigureWorkflowInputs/components/Main/components/Stepper/steps/constants"; -import { Props } from "../../../pages/data/[entityListType]/[entityId]/[trsId]"; import { getAssembly } from "../../services/workflows/entities"; import { getWorkflow } from "../../services/workflows/entities"; diff --git a/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx b/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx index a2aacd8c..c5ebc145 100644 --- a/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx +++ b/pages/data/[entityListType]/[entityId]/[trsId]/index.tsx @@ -31,7 +31,7 @@ interface PageUrlParams extends ParsedUrlQuery { trsId: string; } -export interface Props { +interface Props { entityId: string; entityListType: string; trsId: string;