diff --git a/src/App.test.tsx b/src/App.test.tsx
index b09d4924..1dbdc25e 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,11 +1,11 @@
+import { useMediaQuery } from '@mui/material';
+import { act, fireEvent, render, screen } from '@testing-library/react';
+import axios from 'axios';
import React from 'react';
import { createRoot } from 'react-dom/client';
import App, { AppSansHoc } from './App';
-import { act, fireEvent, render, screen } from '@testing-library/react';
import { flushPromises } from './setupTests';
-import axios from 'axios';
import { RegisterRouteType } from './state/scigateway.types';
-import { useMediaQuery } from '@mui/material';
jest.mock('./state/actions/loadMicroFrontends', () => ({
init: jest.fn(() => Promise.resolve()),
diff --git a/src/adminPage/adminPage.component.test.tsx b/src/adminPage/adminPage.component.test.tsx
index e2aaba7d..76e32697 100644
--- a/src/adminPage/adminPage.component.test.tsx
+++ b/src/adminPage/adminPage.component.test.tsx
@@ -1,19 +1,18 @@
-import React from 'react';
+import { StyledEngineProvider, ThemeProvider } from '@mui/material';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { createLocation, createMemoryHistory, History } from 'history';
+import React from 'react';
+import { Provider } from 'react-redux';
+import { Router } from 'react-router';
+import configureStore from 'redux-mock-store';
+import { thunk } from 'redux-thunk';
+import TestAuthProvider from '../authentication/testAuthProvider';
import { authState, initialState } from '../state/reducers/scigateway.reducer';
-import { StateType } from '../state/state.types';
import { PluginConfig } from '../state/scigateway.types';
-import configureStore from 'redux-mock-store';
-import AdminPage from './adminPage.component';
-import { Provider } from 'react-redux';
+import { StateType } from '../state/state.types';
import { buildTheme } from '../theming';
-import TestAuthProvider from '../authentication/testAuthProvider';
-import { thunk } from 'redux-thunk';
-import { Router } from 'react-router';
-import { StyledEngineProvider, ThemeProvider } from '@mui/material';
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { getPluginRoutes } from './adminPage.component';
+import AdminPage, { getAdminPluginRoutes } from './adminPage.component';
describe('Admin page component', () => {
let mockStore;
@@ -50,9 +49,24 @@ describe('Admin page component', () => {
);
}
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
it('should render maintenance page correctly', () => {
- history.replace('/admin/maintenance');
state.scigateway.adminPageDefaultTab = 'download';
+ state.scigateway.plugins = [
+ ...state.scigateway.plugins,
+ {
+ order: 1,
+ plugin: 'datagateway-download',
+ link: '/admin/download',
+ section: 'Admin',
+ displayName: 'Admin Download',
+ admin: true,
+ },
+ ];
+ history.replace('/admin/maintenance');
render(, { wrapper: Wrapper });
@@ -131,9 +145,58 @@ describe('Admin page component', () => {
).toBeInTheDocument();
});
+ it("falls back to 'maintenance' when adminPageDefaultTab is not provided", () => {
+ state.scigateway.adminPageDefaultTab = undefined;
+ history.replace('/admin');
+
+ render(, { wrapper: Wrapper });
+
+ // Assert that the `maintenance` tab is selected by default
+ expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
+ it("falls back to 'maintenance' when on an invalid route", () => {
+ state.scigateway.plugins = [
+ {
+ order: 1,
+ plugin: 'datagateway-download',
+ link: '/admin/download',
+ section: 'Admin',
+ displayName: 'Admin Download',
+ admin: true,
+ },
+ ];
+ state.scigateway.adminPageDefaultTab = 'maintenance';
+ history.replace('/admin/test');
+
+ render(, { wrapper: Wrapper });
+
+ // Assert that the `maintenance` tab is selected by default
+ expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
+ it("falls back to 'maintenance' when adminPageDefaultTab doesn't match any key in adminRoutes", () => {
+ state.scigateway.adminPageDefaultTab = 'nonexistentTab';
+ history.replace('/admin');
+
+ render(, { wrapper: Wrapper });
+
+ // Assert that the `maintenance` tab is selected by default
+ expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
it('should return an empty object when given an empty plugins array', () => {
const plugins = [];
- const result = getPluginRoutes(plugins);
+ const result = getAdminPluginRoutes({ plugins });
expect(result).toEqual({});
});
@@ -164,9 +227,9 @@ describe('Admin page component', () => {
order: 3,
},
];
- const result = getPluginRoutes(plugins, true); // Admin user
+ const result = getAdminPluginRoutes({ plugins }); // Admin user
expect(result).toEqual({
- PluginA: ['/admin/pluginA', '/admin/pluginA2'],
+ PluginA: { pluginA: '/admin/pluginA', pluginA2: '/admin/pluginA2' },
});
});
@@ -175,7 +238,7 @@ describe('Admin page component', () => {
{
plugin: 'PluginA',
admin: true,
- link: '/admin/pluginA',
+ link: '/admin/pluginALink',
section: 'A',
displayName: 'A',
order: 1,
@@ -183,15 +246,17 @@ describe('Admin page component', () => {
{
plugin: 'PluginB',
admin: false,
- link: '/public/pluginB',
+ link: '/public/pluginBLink',
section: 'B',
displayName: 'B',
order: 2,
},
];
- const result = getPluginRoutes(plugins, false); // Non-admin user
+ const result = getAdminPluginRoutes({ plugins }); // Non-admin user
expect(result).toEqual({
- PluginB: ['/public/pluginB'],
+ PluginA: {
+ pluginALink: '/admin/pluginALink',
+ },
});
});
});
diff --git a/src/adminPage/adminPage.component.tsx b/src/adminPage/adminPage.component.tsx
index d5adbf23..5739de2e 100644
--- a/src/adminPage/adminPage.component.tsx
+++ b/src/adminPage/adminPage.component.tsx
@@ -1,55 +1,67 @@
-import React, { ReactElement } from 'react';
-import Typography from '@mui/material/Typography';
import { Paper } from '@mui/material';
+import Typography from '@mui/material/Typography';
+import React, { ReactElement } from 'react';
import { connect } from 'react-redux';
+import { PluginConfig } from '../state/scigateway.types';
import { StateType } from '../state/state.types';
-import { adminRoutes, PluginConfig } from '../state/scigateway.types';
-import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
+import Tabs from '@mui/material/Tabs';
+import { useTranslation } from 'react-i18next';
import { Link, Route, Switch, useLocation } from 'react-router-dom';
import PageNotFound from '../pageNotFound/pageNotFound.component';
-import { PluginPlaceHolder } from '../routing/routing.component';
+import {
+ getAdminRoutes,
+ PluginPlaceHolder,
+} from '../routing/routing.component';
import MaintenancePage from './maintenancePage.component';
-import { useTranslation } from 'react-i18next';
export interface AdminPageProps {
plugins: PluginConfig[];
- adminPageDefaultTab?: 'maintenance' | 'download';
+ adminPageDefaultTab?: string;
}
-export const getPluginRoutes = (
- plugins: PluginConfig[],
- admin?: boolean
-): Record => {
- const pluginRoutes: Record = {};
+export const getAdminPluginRoutes = (props: {
+ plugins: PluginConfig[];
+}): Record> => {
+ const { plugins } = props;
+ const pluginRoutes: Record> = {};
plugins.forEach((p) => {
- const isAdmin = admin ? p.admin : !p.admin;
const basePluginLink = p.link.split('?')[0];
- if (isAdmin) {
- if (pluginRoutes[p.plugin]) {
- pluginRoutes[p.plugin].push(basePluginLink);
- } else {
- pluginRoutes[p.plugin] = [basePluginLink];
+
+ if (p.admin) {
+ // Extract `plugin` and `tabName` values from the link
+ const tabName = basePluginLink.split('/')[2]; // Ignore `/admin` part, get the tabName as the third part
+
+ // Initialize nested structure for each plugin and tabName
+ if (!pluginRoutes[p.plugin]) {
+ pluginRoutes[p.plugin] = {};
+ }
+
+ // Only store the first route (or the most relevant one)
+ if (!pluginRoutes[p.plugin][tabName]) {
+ pluginRoutes[p.plugin][tabName] = basePluginLink;
}
}
});
+
return pluginRoutes;
};
const AdminPage = (props: AdminPageProps): ReactElement => {
- const pluginRoutes = getPluginRoutes(props.plugins, true);
+ const pluginRoutes = getAdminPluginRoutes({ plugins: props.plugins });
+ const adminRoutes = getAdminRoutes({ plugins: props.plugins });
const location = useLocation();
-
- const [tabValue, setTabValue] = React.useState<'maintenance' | 'download'>(
- // allows direct access to a tab when another tab is the default
- (Object.keys(adminRoutes) as (keyof typeof adminRoutes)[]).find(
- (key) => adminRoutes[key] === location.pathname
+ const [tabValue, setTabValue] = React.useState(
+ (Object.keys(adminRoutes) as (keyof typeof adminRoutes)[]).find((key) =>
+ location.pathname.startsWith(adminRoutes[key])
) ??
- props.adminPageDefaultTab ??
- 'maintenance'
+ (props.adminPageDefaultTab &&
+ adminRoutes.hasOwnProperty(props.adminPageDefaultTab)
+ ? props.adminPageDefaultTab
+ : 'maintenance')
);
const [t] = useTranslation();
@@ -76,22 +88,26 @@ const AdminPage = (props: AdminPageProps): ReactElement => {
setTabValue(newValue);
}}
>
-
-
+ {Object.entries(adminRoutes).map(([key, value]) => {
+ const pluginDetails = props.plugins.find(
+ (plugin) => plugin.link === value
+ );
+
+ return (
+
+ );
+ })}
@@ -105,20 +121,21 @@ const AdminPage = (props: AdminPageProps): ReactElement => {
- {Object.entries(pluginRoutes).map(([key, value]) => {
- return (
-
+ {Object.entries(pluginRoutes).map(([pluginName, tabRoutes]) =>
+ Object.entries(tabRoutes).map(([tabName, route]) => (
+
- );
- })}
+ ))
+ )}
+
diff --git a/src/mainAppBar/mainAppBar.component.test.tsx b/src/mainAppBar/mainAppBar.component.test.tsx
index a1aefd2e..44fd1bcf 100644
--- a/src/mainAppBar/mainAppBar.component.test.tsx
+++ b/src/mainAppBar/mainAppBar.component.test.tsx
@@ -1,25 +1,25 @@
-import React from 'react';
-import MainAppBarComponent from './mainAppBar.component';
+import { useMediaQuery } from '@mui/material';
+import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
+import { render, screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { push } from 'connected-react-router';
import { createLocation, createMemoryHistory, History } from 'history';
-import { StateType } from '../state/state.types';
-import { PluginConfig } from '../state/scigateway.types';
+import React from 'react';
+import { Provider } from 'react-redux';
+import { Router } from 'react-router-dom';
import configureStore, { MockStore } from 'redux-mock-store';
-import { push } from 'connected-react-router';
-import { initialState } from '../state/reducers/scigateway.reducer';
+import TestAuthProvider from '../authentication/testAuthProvider';
import {
loadDarkModePreference,
loadHighContrastModePreference,
toggleDrawer,
toggleHelp,
} from '../state/actions/scigateway.actions';
-import { Provider } from 'react-redux';
-import TestAuthProvider from '../authentication/testAuthProvider';
+import { initialState } from '../state/reducers/scigateway.reducer';
+import { PluginConfig } from '../state/scigateway.types';
+import { StateType } from '../state/state.types';
import { buildTheme } from '../theming';
-import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
-import { Router } from 'react-router-dom';
-import { render, screen, waitFor, within } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { useMediaQuery } from '@mui/material';
+import MainAppBarComponent from './mainAppBar.component';
jest.mock('@mui/material', () => ({
__esmodule: true,
@@ -219,6 +219,17 @@ describe('Main app bar component', () => {
it('redirects to Admin page when Admin button clicked (download is default)', async () => {
state.scigateway.adminPageDefaultTab = 'download';
+ state.scigateway.plugins = [
+ ...state.scigateway.plugins,
+ {
+ section: 'Admin',
+ link: '/admin/download',
+ displayName: 'Admin Download',
+ admin: true,
+ order: 1,
+ plugin: 'plugin',
+ },
+ ];
const user = userEvent.setup();
render(, { wrapper: Wrapper });
diff --git a/src/mainAppBar/mainAppBar.component.tsx b/src/mainAppBar/mainAppBar.component.tsx
index eb727df6..56ace706 100644
--- a/src/mainAppBar/mainAppBar.component.tsx
+++ b/src/mainAppBar/mainAppBar.component.tsx
@@ -1,37 +1,36 @@
-import React, { useState } from 'react';
-import { Dispatch, Action } from 'redux';
-import { connect } from 'react-redux';
-import AppBar from '@mui/material/AppBar';
-import Toolbar from '@mui/material/Toolbar';
-import Button from '@mui/material/Button';
-import IconButton from '@mui/material/IconButton';
import HelpIcon from '@mui/icons-material/HelpOutline';
import MenuIcon from '@mui/icons-material/Menu';
-import SettingsIcon from '@mui/icons-material/Settings';
+import MenuOpenIcon from '@mui/icons-material/MenuOpen';
import MoreVertIcon from '@mui/icons-material/MoreVert';
+import SettingsIcon from '@mui/icons-material/Settings';
import { Box, styled, useMediaQuery } from '@mui/material';
-import MenuOpenIcon from '@mui/icons-material/MenuOpen';
+import AppBar from '@mui/material/AppBar';
+import Button from '@mui/material/Button';
+import IconButton from '@mui/material/IconButton';
import { Theme, useTheme } from '@mui/material/styles';
+import Toolbar from '@mui/material/Toolbar';
+import { push } from 'connected-react-router';
+import React, { useState } from 'react';
+import { connect } from 'react-redux';
+import { useLocation } from 'react-router-dom';
+import { Action, Dispatch } from 'redux';
+import NullAuthProvider from '../authentication/nullAuthProvider';
import ScigatewayLogo from '../images/scigateway-white-text-blue-mark-logo.svg';
+import NotificationBadgeComponent from '../notifications/notificationBadge.component';
import {
- toggleDrawer,
- toggleHelp,
loadDarkModePreference,
loadHighContrastModePreference,
+ toggleDrawer,
+ toggleHelp,
} from '../state/actions/scigateway.actions';
-import { AppStrings } from '../state/scigateway.types';
+import { AppStrings, PluginConfig } from '../state/scigateway.types';
import { StateType } from '../state/state.types';
-import { push } from 'connected-react-router';
import { getAppStrings, getString } from '../state/strings';
-import UserProfileComponent from './userProfile.component';
-import NotificationBadgeComponent from '../notifications/notificationBadge.component';
-import { PluginConfig } from '../state/scigateway.types';
-import { useLocation } from 'react-router-dom';
-import SettingsMenu from './settingsMenu.component';
import MobileOverflowMenu from './mobileOverflowMenu.component';
-import { appBarIconButtonStyle, appBarMenuItemIconStyle } from './styles';
import PageLinks from './pageLinks.component';
-import NullAuthProvider from '../authentication/nullAuthProvider';
+import SettingsMenu from './settingsMenu.component';
+import { appBarIconButtonStyle, appBarMenuItemIconStyle } from './styles';
+import UserProfileComponent from './userProfile.component';
interface MainAppProps {
drawerOpen: boolean;
@@ -47,7 +46,7 @@ interface MainAppProps {
loading: boolean;
logo?: string;
homepageUrl?: string;
- adminPageDefaultTab?: 'maintenance' | 'download';
+ adminPageDefaultTab?: string;
pathname: string;
}
diff --git a/src/mainAppBar/mobileOverflowMenu.component.test.tsx b/src/mainAppBar/mobileOverflowMenu.component.test.tsx
index be298589..b62d9da1 100644
--- a/src/mainAppBar/mobileOverflowMenu.component.test.tsx
+++ b/src/mainAppBar/mobileOverflowMenu.component.test.tsx
@@ -1,20 +1,20 @@
-import configureStore, { MockStore } from 'redux-mock-store';
-import { StateType } from '../state/state.types';
-import { initialState } from '../state/reducers/scigateway.reducer';
+import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { push } from 'connected-react-router';
import { createLocation, createMemoryHistory, History } from 'history';
import * as React from 'react';
-import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
-import { buildTheme } from '../theming';
-import { render, screen } from '@testing-library/react';
-import MobileOverflowMenu from './mobileOverflowMenu.component';
+import configureStore, { MockStore } from 'redux-mock-store';
import TestAuthProvider, {
NonAdminTestAuthProvider,
} from '../authentication/testAuthProvider';
-import userEvent from '@testing-library/user-event';
-import { push } from 'connected-react-router';
import { toggleHelp } from '../state/actions/scigateway.actions';
+import { initialState } from '../state/reducers/scigateway.reducer';
+import { StateType } from '../state/state.types';
+import { buildTheme } from '../theming';
+import MobileOverflowMenu from './mobileOverflowMenu.component';
describe('Mobile overflow menu', () => {
let testStore: MockStore;
@@ -99,6 +99,17 @@ describe('Mobile overflow menu', () => {
it('redirects to Admin page when Admin button clicked (download is default)', async () => {
state.scigateway.adminPageDefaultTab = 'download';
+ state.scigateway.plugins = [
+ ...state.scigateway.plugins,
+ {
+ section: 'Admin',
+ link: '/admin/download',
+ displayName: 'Admin Download',
+ admin: true,
+ order: 1,
+ plugin: 'plugin',
+ },
+ ];
const user = userEvent.setup();
render(, {
diff --git a/src/mainAppBar/mobileOverflowMenu.component.tsx b/src/mainAppBar/mobileOverflowMenu.component.tsx
index e6754cbc..5ef77b26 100644
--- a/src/mainAppBar/mobileOverflowMenu.component.tsx
+++ b/src/mainAppBar/mobileOverflowMenu.component.tsx
@@ -1,18 +1,18 @@
import React from 'react';
import {
+ Divider,
+ ListItemText,
Menu,
MenuItem,
- ListItemText,
MenuProps,
- Divider,
} from '@mui/material';
-import { SettingsMenuContent } from './settingsMenu.component';
+import { push } from 'connected-react-router';
import { useDispatch, useSelector } from 'react-redux';
+import { getAdminRoutes } from '../routing/routing.component';
+import { toggleHelp } from '../state/actions/scigateway.actions';
import { StateType } from '../state/state.types';
import { getAppStrings, getString } from '../state/strings';
-import { push } from 'connected-react-router';
-import { adminRoutes } from '../state/scigateway.types';
-import { toggleHelp } from '../state/actions/scigateway.actions';
+import { SettingsMenuContent } from './settingsMenu.component';
interface MobileOverflowMenuProps extends MenuProps {
onClose: () => void;
@@ -39,6 +39,9 @@ function MobileOverflowMenu({
(state: StateType) => state.scigateway.adminPageDefaultTab
);
+ const plugins = useSelector((state: StateType) => state.scigateway.plugins);
+ const adminRoutes = getAdminRoutes({ plugins });
+
const dispatch = useDispatch();
function navigateToHelpPage(): void {
@@ -46,7 +49,12 @@ function MobileOverflowMenu({
}
function navigateToAdminPage(): void {
- dispatch(push(adminRoutes[adminPageDefaultTab ?? 'maintenance']));
+ const targetRoute =
+ adminPageDefaultTab && adminRoutes.hasOwnProperty(adminPageDefaultTab)
+ ? adminRoutes[adminPageDefaultTab]
+ : adminRoutes['maintenance'];
+
+ dispatch(push(targetRoute));
}
function toggleTutorial(): void {
diff --git a/src/mainAppBar/pageLinks.component.tsx b/src/mainAppBar/pageLinks.component.tsx
index b126ae96..7de64c33 100644
--- a/src/mainAppBar/pageLinks.component.tsx
+++ b/src/mainAppBar/pageLinks.component.tsx
@@ -1,12 +1,12 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import Typography from '@mui/material/Typography';
+import { push } from 'connected-react-router';
import { useDispatch, useSelector } from 'react-redux';
+import { getAdminRoutes } from '../routing/routing.component';
import { StateType } from '../state/state.types';
-import Button from '@mui/material/Button';
import { getAppStrings, getString } from '../state/strings';
-import Typography from '@mui/material/Typography';
-import React from 'react';
import { appBarIconButtonStyle } from './styles';
-import { push } from 'connected-react-router';
-import { adminRoutes } from '../state/scigateway.types';
function PageLinks(): JSX.Element {
const shouldShowHelpPageButton = useSelector(
@@ -20,6 +20,10 @@ function PageLinks(): JSX.Element {
const adminPageDefaultTab = useSelector(
(state: StateType) => state.scigateway.adminPageDefaultTab
);
+
+ const plugins = useSelector((state: StateType) => state.scigateway.plugins);
+ const adminRoutes = getAdminRoutes({ plugins });
+
const res = useSelector((state: StateType) =>
getAppStrings(state, 'main-appbar')
);
@@ -31,7 +35,12 @@ function PageLinks(): JSX.Element {
}
function navigateToAdminPage(): void {
- dispatch(push(adminRoutes[adminPageDefaultTab ?? 'maintenance']));
+ const targetRoute =
+ adminPageDefaultTab && adminRoutes.hasOwnProperty(adminPageDefaultTab)
+ ? adminRoutes[adminPageDefaultTab]
+ : adminRoutes['maintenance'];
+
+ dispatch(push(targetRoute));
}
return (
diff --git a/src/routing/routing.component.tsx b/src/routing/routing.component.tsx
index 4b83f45d..c955b61e 100644
--- a/src/routing/routing.component.tsx
+++ b/src/routing/routing.component.tsx
@@ -1,29 +1,29 @@
-import React from 'react';
+import { useMediaQuery } from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
-import { Redirect, Route, Switch } from 'react-router-dom';
-import { StateType } from '../state/state.types';
-import {
- adminRoutes,
- MaintenanceState,
- PluginConfig,
- scigatewayRoutes,
-} from '../state/scigateway.types';
+import { RouterLocation } from 'connected-react-router';
+import React from 'react';
import { connect } from 'react-redux';
-import HomePage from '../homePage/homePage.component';
+import { Redirect, Route, Switch } from 'react-router-dom';
+import * as singleSpa from 'single-spa';
+import AccessibilityPage from '../accessibilityPage/accessibilityPage.component';
+import AdminPage from '../adminPage/adminPage.component';
+import NullAuthProvider from '../authentication/nullAuthProvider';
+import CookiesPage from '../cookieConsent/cookiesPage.component';
import HelpPage from '../helpPage/helpPage.component';
+import HomePage from '../homePage/homePage.component';
import LoginPage from '../loginPage/loginPage.component';
import LogoutPage from '../logoutPage/logoutPage.component';
-import CookiesPage from '../cookieConsent/cookiesPage.component';
import MaintenancePage from '../maintenancePage/maintenancePage.component';
-import AdminPage from '../adminPage/adminPage.component';
import PageNotFound from '../pageNotFound/pageNotFound.component';
-import AccessibilityPage from '../accessibilityPage/accessibilityPage.component';
-import withAuth, { usePrevious } from './authorisedRoute.component';
import { Preloader } from '../preloader/preloader.component';
-import * as singleSpa from 'single-spa';
-import { useMediaQuery } from '@mui/material';
-import NullAuthProvider from '../authentication/nullAuthProvider';
-import { RouterLocation } from 'connected-react-router';
+import {
+ baseAdminRoutes,
+ MaintenanceState,
+ PluginConfig,
+ scigatewayRoutes,
+} from '../state/scigateway.types';
+import { StateType } from '../state/state.types';
+import withAuth, { usePrevious } from './authorisedRoute.component';
interface ContainerDivProps {
drawerOpen: boolean;
@@ -68,6 +68,29 @@ const ContainerDiv = styled('div', {
};
});
+export const getAdminRoutes = (props: {
+ plugins: PluginConfig[];
+}): Record => {
+ const { plugins } = props;
+ const newAdminRoutes: Record = JSON.parse(
+ JSON.stringify(baseAdminRoutes)
+ );
+
+ // Note: Any nested paths under `/admin/path` are managed by the plugin itself and
+ // should not be included in the `newAdminRoutes` object. This ensures only top-level
+ // admin routes are added here, keeping the route structure consistent and preventing
+ // conflicts in routing.
+
+ plugins.forEach((plugin) => {
+ if (plugin.admin) {
+ const routeKey = plugin.link.split('/')[2];
+ newAdminRoutes[routeKey] = plugin.link;
+ }
+ });
+
+ return newAdminRoutes;
+};
+
interface RoutingProps {
plugins: PluginConfig[];
location: RouterLocation;
@@ -101,6 +124,7 @@ export const UnauthorisedPlugin = PluginPlaceHolder;
export const AuthorisedAdminPage = withAuth(true)(AdminPage);
const Routing: React.FC = (props: RoutingProps) => {
+ const adminRoutes = getAdminRoutes({ plugins: props.plugins });
// only set to false if we're on a plugin route i.e. not a scigateway route
const manuallyLoadedPluginRef = React.useRef(
Object.values(scigatewayRoutes).includes(props.location.pathname) ||
@@ -137,7 +161,6 @@ const Routing: React.FC = (props: RoutingProps) => {
window.clearInterval(intervalId);
};
}, [props.loading, props.plugins, props.location]);
-
React.useEffect(() => {
// switching between an admin & non-admin route of the same app causes problems
// as the Route and thus the plugin div changes but single-spa doesn't remount
diff --git a/src/state/actions/scigateway.actions.tsx b/src/state/actions/scigateway.actions.tsx
index 5746ccbe..18ef4055 100644
--- a/src/state/actions/scigateway.actions.tsx
+++ b/src/state/actions/scigateway.actions.tsx
@@ -5,6 +5,7 @@ import log from 'loglevel';
import { Step } from 'react-joyride';
import { Action, AnyAction } from 'redux';
import { ThunkAction } from 'redux-thunk';
+import * as singleSpa from 'single-spa';
import {
AddHelpTourStepsPayload,
AddHelpTourStepsType,
@@ -18,15 +19,27 @@ import {
ConfigureFeatureSwitchesType,
ConfigureStringsPayload,
ConfigureStringsType,
+ ContactUsAccessibilityFormUrlPayload,
+ CustomAdminPageDefaultTabPayload,
+ CustomAdminPageDefaultTabType,
+ CustomLogoPayload,
+ CustomLogoType,
+ CustomNavigationDrawerLogoPayload,
+ CustomNavigationDrawerLogoType,
+ CustomPrimaryColourPayload,
+ CustomPrimaryColourType,
DismissNotificationPayload,
DismissNotificationType,
FeatureSwitches,
FeatureSwitchesPayload,
+ HomepageUrlPayload,
InitialiseAnalyticsType,
InvalidateTokenType,
LoadAuthProviderType,
LoadDarkModePreferencePayload,
LoadDarkModePreferenceType,
+ LoadHighContrastModePreferencePayload,
+ LoadHighContrastModePreferenceType,
LoadMaintenanceStateType,
LoadScheduledMaintenanceStateType,
LoadedAuthType,
@@ -34,8 +47,11 @@ import {
MaintenanceState,
MaintenanceStatePayLoad,
NotificationType,
+ RegisterContactUsAccessibilityFormUrlType,
RegisterHomepageUrlType,
+ RegisterRouteType,
RequestPluginRerenderType,
+ ResetAuthStateType,
ScheduledMaintenanceState,
ScheduledMaintenanceStatePayLoad,
SendThemeOptionsPayload,
@@ -43,29 +59,13 @@ import {
SignOutType,
SiteLoadingPayload,
SiteLoadingType,
- HomepageUrlPayload,
- CustomLogoPayload,
ToggleDrawerType,
ToggleHelpType,
- RegisterRouteType,
+ baseAdminRoutes,
scigatewayRoutes,
- CustomLogoType,
- LoadHighContrastModePreferenceType,
- LoadHighContrastModePreferencePayload,
- ResetAuthStateType,
- CustomNavigationDrawerLogoPayload,
- CustomNavigationDrawerLogoType,
- CustomAdminPageDefaultTabPayload,
- CustomAdminPageDefaultTabType,
- RegisterContactUsAccessibilityFormUrlType,
- ContactUsAccessibilityFormUrlPayload,
- adminRoutes,
- CustomPrimaryColourType,
- CustomPrimaryColourPayload,
} from '../scigateway.types';
import { ActionType, LogoState, StateType, ThunkResult } from '../state.types';
import loadMicroFrontends from './loadMicroFrontends';
-import * as singleSpa from 'single-spa';
export const configureStrings = (
appStrings: ApplicationStrings
@@ -124,7 +124,7 @@ export const customNavigationDrawerLogo = (
});
export const customAdminPageDefaultTab = (
- adminPageDefaultTab: 'maintenance' | 'download'
+ adminPageDefaultTab: string
): ActionType => ({
type: CustomAdminPageDefaultTabType,
payload: {
@@ -342,11 +342,7 @@ export const configureSite = (): ThunkResult> => {
dispatch(customPrimaryColour(settings['primaryColour']));
}
- if (
- settings['adminPageDefaultTab'] &&
- (settings['adminPageDefaultTab'].includes('maintenance') ||
- settings['adminPageDefaultTab'].includes('download'))
- ) {
+ if (settings['adminPageDefaultTab']) {
dispatch(customAdminPageDefaultTab(settings['adminPageDefaultTab']));
}
@@ -371,7 +367,7 @@ export const configureSite = (): ThunkResult> => {
const currUrl = getState().router.location.pathname;
if (
!Object.values(scigatewayRoutes).includes(currUrl) &&
- currUrl !== adminRoutes.maintenance &&
+ currUrl !== baseAdminRoutes.maintenance &&
!getState().scigateway.plugins.find((p) =>
currUrl.startsWith(p.link.split('?')[0])
)
diff --git a/src/state/scigateway.types.tsx b/src/state/scigateway.types.tsx
index 7a9f0d13..53004b08 100644
--- a/src/state/scigateway.types.tsx
+++ b/src/state/scigateway.types.tsx
@@ -55,9 +55,8 @@ export const scigatewayRoutes = {
cookies: '/cookies',
accessibility: '/accessibility',
};
-export const adminRoutes = {
+export const baseAdminRoutes = {
maintenance: '/admin/maintenance',
- download: '/admin/download',
};
export interface NotificationPayload {
@@ -99,7 +98,7 @@ export interface CustomNavigationDrawerLogoPayload {
}
export interface CustomAdminPageDefaultTabPayload {
- adminPageDefaultTab: 'maintenance' | 'download';
+ adminPageDefaultTab: string;
}
export interface CustomPrimaryColourPayload {
diff --git a/src/state/state.types.tsx b/src/state/state.types.tsx
index f6ecad05..018d0bca 100644
--- a/src/state/state.types.tsx
+++ b/src/state/state.types.tsx
@@ -1,14 +1,14 @@
-import { ThunkAction } from 'redux-thunk';
-import { AnyAction } from 'redux';
+import { RouterState } from 'connected-react-router';
import { Step } from 'react-joyride';
+import { AnyAction } from 'redux';
+import { ThunkAction } from 'redux-thunk';
import {
ApplicationStrings,
- PluginConfig,
FeatureSwitches,
- ScheduledMaintenanceState,
MaintenanceState,
+ PluginConfig,
+ ScheduledMaintenanceState,
} from './scigateway.types';
-import { RouterState } from 'connected-react-router';
export interface Plugin {
name: string;
@@ -41,7 +41,7 @@ export interface ScigatewayState {
scheduledMaintenance: ScheduledMaintenanceState;
maintenance: MaintenanceState;
navigationDrawerLogo?: LogoState;
- adminPageDefaultTab?: 'maintenance' | 'download';
+ adminPageDefaultTab?: string;
contactUsAccessibilityFormUrl?: string;
primaryColour?: string;
}