diff --git a/src/js/components/BentoAppRouter.tsx b/src/js/components/BentoAppRouter.tsx index 8a2e313d..ac13d523 100644 --- a/src/js/components/BentoAppRouter.tsx +++ b/src/js/components/BentoAppRouter.tsx @@ -33,13 +33,13 @@ const ScopedRoute = () => { const { projectId, datasetId } = useParams(); const dispatch = useAppDispatch(); const navigate = useNavigate(); - const { selectedScope, projects, projectsStatus } = useMetadata(); + const { selectedScope, projectsByID, projectsStatus } = useMetadata(); useEffect(() => { if (WAITING_STATES.includes(projectsStatus)) return; // Wait for projects to load first // Update selectedScope based on URL parameters - const valid = validProjectDataset(projects, { project: projectId, dataset: datasetId }); + const valid = validProjectDataset(projectsByID, { project: projectId, dataset: datasetId }); // Don't change the scope object if the scope value is the same, otherwise it'll trigger needless re-renders. if (scopeEqual(selectedScope.scope, valid.scope)) { @@ -72,7 +72,7 @@ const ScopedRoute = () => { } const newPathString = '/' + newPath.join('/'); navigate(newPathString, { replace: true }); - }, [projects, projectsStatus, projectId, datasetId, dispatch, navigate, selectedScope]); + }, [projectsByID, projectsStatus, projectId, datasetId, dispatch, navigate, selectedScope]); return ; }; diff --git a/src/js/components/Scope/DatasetScopePicker.tsx b/src/js/components/Scope/DatasetScopePicker.tsx index caa4a592..15f1fc96 100644 --- a/src/js/components/Scope/DatasetScopePicker.tsx +++ b/src/js/components/Scope/DatasetScopePicker.tsx @@ -51,7 +51,14 @@ const DatasetScopePicker = ({ parentProject }: DatasetScopePickerProps) => { } + renderItem={(d) => ( + + )} /> ); diff --git a/src/js/features/metadata/metadata.store.ts b/src/js/features/metadata/metadata.store.ts index b7824a37..cbe7c90c 100644 --- a/src/js/features/metadata/metadata.store.ts +++ b/src/js/features/metadata/metadata.store.ts @@ -19,12 +19,14 @@ export type DiscoveryScopeSelection = { export interface MetadataState { projects: Project[]; + projectsByID: Record; projectsStatus: RequestStatus; selectedScope: DiscoveryScopeSelection; } const initialState: MetadataState = { projects: [], + projectsByID: {}, projectsStatus: RequestStatus.Idle, selectedScope: { scope: { project: undefined, dataset: undefined }, @@ -66,7 +68,7 @@ const metadata = createSlice({ // Defaults to the narrowest possible scope if there is only 1 project and only 1 dataset. // This forces Katsu to resolve the Discovery config with fallbacks from the bottom-up: // dataset -> project -> whole node - state.selectedScope = validProjectDataset(state.projects, payload); + state.selectedScope = validProjectDataset(state.projectsByID, payload); }, markScopeSet: (state) => { state.selectedScope.scopeSet = true; @@ -77,7 +79,9 @@ const metadata = createSlice({ state.projectsStatus = RequestStatus.Pending; }); builder.addCase(getProjects.fulfilled, (state, { payload }) => { - state.projects = payload?.results ?? []; + const projects = payload?.results ?? []; + state.projects = projects; + state.projectsByID = Object.fromEntries(projects.map((p) => [p.identifier, p])); state.projectsStatus = RequestStatus.Fulfilled; }); builder.addCase(getProjects.rejected, (state) => { diff --git a/src/js/utils/router.ts b/src/js/utils/router.ts index fb098e30..64b4c3e2 100644 --- a/src/js/utils/router.ts +++ b/src/js/utils/router.ts @@ -12,7 +12,10 @@ export const getCurrentPage = (): string => { } }; -export const validProjectDataset = (projects: Project[], unvalidatedScope: DiscoveryScope): DiscoveryScopeSelection => { +export const validProjectDataset = ( + projectsByID: Record, + unvalidatedScope: DiscoveryScope +): DiscoveryScopeSelection => { const { project, dataset } = unvalidatedScope; const valid: DiscoveryScopeSelection = { @@ -22,12 +25,16 @@ export const validProjectDataset = (projects: Project[], unvalidatedScope: Disco fixedDataset: false, }; + const projects = Object.values(projectsByID); + if (projects.length === 1) { - // automatic project scoping if only 1 + // Automatic project scoping if only 1 + // - if there is only one project, it should be auto-selected, since it contains the same set of data as the node. const defaultProj = projects[0]; valid.scope.project = defaultProj.identifier; valid.fixedProject = true; if (defaultProj.datasets.length === 1) { + // TODO: only if the dataset-level permissions equal the project-level ones... // automatic dataset scoping if only 1 valid.scope.dataset = defaultProj.datasets[0].identifier; valid.fixedDataset = true; @@ -35,16 +42,13 @@ export const validProjectDataset = (projects: Project[], unvalidatedScope: Disco return valid; } } - if (project && projects.find(({ identifier }) => identifier === project)) { + + const selectedProject: Project | undefined = project ? projectsByID[project] : undefined; + + if (project && selectedProject) { valid.scope.project = project; - if (dataset) { - if ( - projects - .find(({ identifier }) => identifier === project)! - .datasets.find(({ identifier }) => identifier === dataset) - ) { - valid.scope.dataset = dataset; - } + if (dataset && selectedProject.datasets.find(({ identifier }) => identifier === dataset)) { + valid.scope.dataset = dataset; } } return valid;