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;