diff --git a/frontend/README.md b/frontend/README.md index c75f08d563..a19aa77df3 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -66,3 +66,7 @@ Whenever there are changes to the backend API, the client should be regenerated: This script assumes that you have a running instance of the enterprise backend at `http://localhost:4242`. The new OpenAPI client will be generated from the runtime schema of this instance. The target URL can be changed by setting the `UNLEASH_OPENAPI_URL` env var. + +## Analyzing bundle size + +`npx vite-bundle-visualizer` in the root of the frontend directory diff --git a/frontend/package.json b/frontend/package.json index d11a1feda5..1328645ed8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ "start": "vite", "start:prod": "vite build && vite preview", "start:sandbox": "UNLEASH_API=https://sandbox.getunleash.io/ospro yarn run start", - "start:enterprise": "UNLEASH_API=https://unleash4.herokuapp.com yarn run start", + "start:enterprise": "UNLEASH_API=https://unleash.herokuapp.com yarn run start", "start:demo": "UNLEASH_BASE_PATH=/demo/ yarn start", "test": "tsc && vitest run", "test:snapshot": "yarn test -u", diff --git a/frontend/src/assets/img/changeRequestProcess.svg b/frontend/src/assets/img/changeRequestProcess.svg index 61b71d5473..11df782e1a 100644 --- a/frontend/src/assets/img/changeRequestProcess.svg +++ b/frontend/src/assets/img/changeRequestProcess.svg @@ -1,48 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/frontend/src/component/admin/Admin.tsx b/frontend/src/component/admin/Admin.tsx new file mode 100644 index 0000000000..dce5b15f0f --- /dev/null +++ b/frontend/src/component/admin/Admin.tsx @@ -0,0 +1,52 @@ +import { Routes, Route } from 'react-router-dom'; +import { ApiPage } from './api'; +import { CreateApiToken } from './apiToken/CreateApiToken/CreateApiToken'; +import { AuthSettings } from './auth/AuthSettings'; +import { Billing } from './billing/Billing'; +import FlaggedBillingRedirect from './billing/FlaggedBillingRedirect/FlaggedBillingRedirect'; +import { CorsAdmin } from './cors'; +import { CreateGroup } from './groups/CreateGroup/CreateGroup'; +import { EditGroup } from './groups/EditGroup/EditGroup'; +import { Group } from './groups/Group/Group'; +import { GroupsAdmin } from './groups/GroupsAdmin'; +import { InstanceAdmin } from './instance-admin/InstanceAdmin'; +import { MaintenanceAdmin } from './maintenance'; +import AdminMenu from './menu/AdminMenu'; +import { Network } from './network/Network'; +import CreateProjectRole from './projectRoles/CreateProjectRole/CreateProjectRole'; +import EditProjectRole from './projectRoles/EditProjectRole/EditProjectRole'; +import ProjectRoles from './projectRoles/ProjectRoles/ProjectRoles'; +import { ServiceAccounts } from './serviceAccounts/ServiceAccounts'; +import CreateUser from './users/CreateUser/CreateUser'; +import EditUser from './users/EditUser/EditUser'; +import { InviteLink } from './users/InviteLink/InviteLink'; +import UsersAdmin from './users/UsersAdmin'; + +export const Admin = () => ( + <> + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + +); diff --git a/frontend/src/component/admin/AdminRedirect.tsx b/frontend/src/component/admin/AdminRedirect.tsx new file mode 100644 index 0000000000..9ceaa4bdde --- /dev/null +++ b/frontend/src/component/admin/AdminRedirect.tsx @@ -0,0 +1,3 @@ +import { Navigate } from 'react-router-dom'; + +export const AdminRedirect = () => ; diff --git a/frontend/src/component/admin/LazyAdmin.tsx b/frontend/src/component/admin/LazyAdmin.tsx new file mode 100644 index 0000000000..c7776620c5 --- /dev/null +++ b/frontend/src/component/admin/LazyAdmin.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const LazyAdmin = lazy(() => import('./LazyAdminExport')); diff --git a/frontend/src/component/admin/LazyAdminExport.tsx b/frontend/src/component/admin/LazyAdminExport.tsx new file mode 100644 index 0000000000..e2e2d27958 --- /dev/null +++ b/frontend/src/component/admin/LazyAdminExport.tsx @@ -0,0 +1,7 @@ +// Lazy loading only supports default export. We have an ADR +// that says we should only use named exports, because +// it makes it harder to diverge from the name of the component when +// importing it in other files. +import { Admin } from './Admin'; + +export default Admin; diff --git a/frontend/src/component/admin/api/index.tsx b/frontend/src/component/admin/api/index.tsx index 057133471e..65ddf60ed5 100644 --- a/frontend/src/component/admin/api/index.tsx +++ b/frontend/src/component/admin/api/index.tsx @@ -1,18 +1,8 @@ -import { useLocation } from 'react-router-dom'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; -import AdminMenu from '../menu/AdminMenu'; - -const ApiPage = () => { - const { pathname } = useLocation(); - const showAdminMenu = pathname.includes('/admin/'); +export const ApiPage = () => { return (
- } - />
); diff --git a/frontend/src/component/admin/auth/AuthSettings.tsx b/frontend/src/component/admin/auth/AuthSettings.tsx index e95d9e8f6b..df267cae2d 100644 --- a/frontend/src/component/admin/auth/AuthSettings.tsx +++ b/frontend/src/component/admin/auth/AuthSettings.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import AdminMenu from '../menu/AdminMenu'; import { Alert } from '@mui/material'; import { PageContent } from 'component/common/PageContent/PageContent'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; @@ -34,7 +33,6 @@ export const AuthSettings = () => { return (
- { return (
- { - const { pathname } = useLocation(); - const showAdminMenu = pathname.includes('/admin/'); const { hasAccess } = useContext(AccessContext); return (
- } - /> } diff --git a/frontend/src/component/admin/groups/GroupsAdmin.tsx b/frontend/src/component/admin/groups/GroupsAdmin.tsx index ffe8b39814..8c4f06454d 100644 --- a/frontend/src/component/admin/groups/GroupsAdmin.tsx +++ b/frontend/src/component/admin/groups/GroupsAdmin.tsx @@ -1,10 +1,8 @@ import { GroupsList } from './GroupsList/GroupsList'; -import AdminMenu from '../menu/AdminMenu'; export const GroupsAdmin = () => { return (
-
); diff --git a/frontend/src/component/admin/index.tsx b/frontend/src/component/admin/index.tsx deleted file mode 100644 index 6605f52f9f..0000000000 --- a/frontend/src/component/admin/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Navigate } from 'react-router-dom'; - -const render = () => ; - -render.propTypes = { - match: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, -}; - -export default render; diff --git a/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx b/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx index f8a0248085..3f1fee32ce 100644 --- a/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx +++ b/frontend/src/component/admin/instance-admin/InstanceAdmin.tsx @@ -1,10 +1,8 @@ -import AdminMenu from '../menu/AdminMenu'; import { InstanceStats } from './InstanceStats/InstanceStats'; export const InstanceAdmin = () => { return (
-
); diff --git a/frontend/src/component/admin/maintenance/index.tsx b/frontend/src/component/admin/maintenance/index.tsx index 46b99cfa90..f389597284 100644 --- a/frontend/src/component/admin/maintenance/index.tsx +++ b/frontend/src/component/admin/maintenance/index.tsx @@ -1,6 +1,4 @@ -import { useLocation } from 'react-router-dom'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import AdminMenu from '../menu/AdminMenu'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import AccessContext from 'contexts/AccessContext'; @@ -13,16 +11,10 @@ import { MaintenanceTooltip } from './MaintenanceTooltip'; import { MaintenanceToggle } from './MaintenanceToggle'; export const MaintenanceAdmin = () => { - const { pathname } = useLocation(); - const showAdminMenu = pathname.includes('/admin/'); const { hasAccess } = useContext(AccessContext); return (
- } - /> } diff --git a/frontend/src/component/admin/network/Network.tsx b/frontend/src/component/admin/network/Network.tsx index 56af659084..9a7e610466 100644 --- a/frontend/src/component/admin/network/Network.tsx +++ b/frontend/src/component/admin/network/Network.tsx @@ -1,5 +1,5 @@ import { lazy } from 'react'; -import AdminMenu from '../menu/AdminMenu'; + import { styled, Tab, Tabs } from '@mui/material'; import { Route, Routes, useLocation } from 'react-router-dom'; import { CenteredNavLink } from '../menu/CenteredNavLink'; @@ -30,7 +30,6 @@ export const Network = () => { return (
- { return (
- } diff --git a/frontend/src/component/admin/serviceAccounts/ServiceAccounts.tsx b/frontend/src/component/admin/serviceAccounts/ServiceAccounts.tsx index 8d9cee3ac0..90108d7dc2 100644 --- a/frontend/src/component/admin/serviceAccounts/ServiceAccounts.tsx +++ b/frontend/src/component/admin/serviceAccounts/ServiceAccounts.tsx @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import AdminMenu from '../menu/AdminMenu'; import AccessContext from 'contexts/AccessContext'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; @@ -11,7 +10,6 @@ export const ServiceAccounts = () => { return (
- } diff --git a/frontend/src/component/admin/users/UsersAdmin.tsx b/frontend/src/component/admin/users/UsersAdmin.tsx index 4126d6c830..86a3d42e78 100644 --- a/frontend/src/component/admin/users/UsersAdmin.tsx +++ b/frontend/src/component/admin/users/UsersAdmin.tsx @@ -1,18 +1,16 @@ import { useContext } from 'react'; import UsersList from './UsersList/UsersList'; -import AdminMenu from '../menu/AdminMenu'; import AccessContext from 'contexts/AccessContext'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { InviteLinkBar } from './InviteLinkBar/InviteLinkBar'; -const UsersAdmin = () => { +export const UsersAdmin = () => { const { hasAccess } = useContext(AccessContext); return (
- (r: IRoute) => { - if (r.flag) { - // Check if the route's `flag` is enabled in IUiConfig.flags. - const flags = config.flags as unknown as Record; - return Boolean(flags[r.flag]); - } +export const filterByConfig = + (config: IUiConfig) => (r: INavigationMenuItem) => { + if (r.flag) { + // Check if the route's `flag` is enabled in IUiConfig.flags. + const flags = config.flags as unknown as Record; + return Boolean(flags[r.flag]); + } - if (r.configFlag) { - // Check if the route's `configFlag` is enabled in IUiConfig. - return Boolean(config[r.configFlag]); - } + if (r.configFlag) { + // Check if the route's `configFlag` is enabled in IUiConfig. + return Boolean(config[r.configFlag]); + } - return true; -}; + return true; + }; export const scrollToTop = () => { window.scrollTo(0, 0); diff --git a/frontend/src/component/feature/FeatureView/FeatureViewLazyExport.tsx b/frontend/src/component/feature/FeatureView/FeatureViewLazyExport.tsx new file mode 100644 index 0000000000..c5afcda1b1 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureViewLazyExport.tsx @@ -0,0 +1,7 @@ +// Lazy loading only supports default export. We have an ADR +// that says we should only use named exports, because +// it makes it harder to diverge from the name of the component when +// importing it in other files. +import { FeatureView } from './FeatureView'; + +export default FeatureView; diff --git a/frontend/src/component/feature/FeatureView/LazyFeatureView.tsx b/frontend/src/component/feature/FeatureView/LazyFeatureView.tsx new file mode 100644 index 0000000000..52fcba51e6 --- /dev/null +++ b/frontend/src/component/feature/FeatureView/LazyFeatureView.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const LazyFeatureView = lazy(() => import('./FeatureViewLazyExport')); diff --git a/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx index ebb1683171..9b7b2da947 100644 --- a/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx +++ b/frontend/src/component/menu/Header/DrawerMenu/DrawerMenu.tsx @@ -9,7 +9,7 @@ import NavigationLink from '../NavigationLink/NavigationLink'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { basePath } from 'utils/formatPath'; import { IFlags } from 'interfaces/uiConfig'; -import { IRoute } from 'interfaces/route'; +import { INavigationMenuItem } from 'interfaces/route'; import styles from './DrawerMenu.module.scss'; // FIXME: useStyle - theme interface IDrawerMenuProps { @@ -25,9 +25,9 @@ interface IDrawerMenuProps { }>; flags?: IFlags; routes: { - mainNavRoutes: IRoute[]; - mobileRoutes: IRoute[]; - adminRoutes: IRoute[]; + mainNavRoutes: INavigationMenuItem[]; + mobileRoutes: INavigationMenuItem[]; + adminRoutes: INavigationMenuItem[]; }; } diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index e09d8296ab..f9edf1d958 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -26,12 +26,16 @@ import { flexRow, focusable } from 'themes/themeStyles'; import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { IPermission } from 'interfaces/user'; import { NavigationMenu } from './NavigationMenu/NavigationMenu'; -import { getRoutes } from 'component/menu/routes'; +import { + getRoutes, + adminMenuRoutes, + getCondensedRoutes, +} from 'component/menu/routes'; import { KeyboardArrowDown } from '@mui/icons-material'; import { filterByConfig } from 'component/common/util'; import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions'; import { useId } from 'hooks/useId'; -import { IRoute } from 'interfaces/route'; +import { INavigationMenuItem } from 'interfaces/route'; import { ThemeMode } from 'component/common/ThemeMode/ThemeMode'; import { useThemeMode } from 'hooks/useThemeMode'; @@ -134,14 +138,24 @@ const Header: VFC = () => { const routes = getRoutes(); - const filterByEnterprise = (route: IRoute): boolean => { + const filterByEnterprise = (route: INavigationMenuItem): boolean => { return !route.menu.isEnterprise || !isOss(); }; const filteredMainRoutes = { - mainNavRoutes: routes.mainNavRoutes.filter(filterByConfig(uiConfig)), - mobileRoutes: routes.mobileRoutes.filter(filterByConfig(uiConfig)), - adminRoutes: routes.adminRoutes + mainNavRoutes: getCondensedRoutes(routes.mainNavRoutes) + .concat([ + { + path: '/admin/api', + title: 'API access', + menu: {}, + }, + ]) + .filter(filterByConfig(uiConfig)), + mobileRoutes: getCondensedRoutes(routes.mobileRoutes).filter( + filterByConfig(uiConfig) + ), + adminRoutes: adminMenuRoutes .filter(filterByConfig(uiConfig)) .filter(filterByEnterprise) .map(route => ({ diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index d475ed0a3c..5bb2a14ee6 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -11,7 +11,14 @@ exports[`returns all baseRoutes 1`] = ` "type": "protected", }, { - "component": [Function], + "component": { + "$$typeof": Symbol(react.lazy), + "_init": [Function], + "_payload": { + "_result": [Function], + "_status": -1, + }, + }, "enterprise": true, "menu": {}, "parent": "/projects", @@ -53,7 +60,14 @@ exports[`returns all baseRoutes 1`] = ` "type": "protected", }, { - "component": [Function], + "component": { + "$$typeof": Symbol(react.lazy), + "_init": [Function], + "_payload": { + "_result": [Function], + "_status": -1, + }, + }, "menu": {}, "parent": "/projects", "path": "/projects/:projectId/features/:featureId/*", @@ -77,7 +91,14 @@ exports[`returns all baseRoutes 1`] = ` "type": "protected", }, { - "component": [Function], + "component": { + "$$typeof": Symbol(react.lazy), + "_init": [Function], + "_payload": { + "_result": [Function], + "_status": -1, + }, + }, "flag": "P", "menu": {}, "parent": "/projects", @@ -333,199 +354,6 @@ exports[`returns all baseRoutes 1`] = ` "title": "Archived toggles", "type": "protected", }, - { - "component": [Function], - "menu": {}, - "parent": "/admin", - "path": "/admin/api/create-token", - "title": "API access", - "type": "protected", - }, - { - "component": [Function], - "flag": "RE", - "menu": {}, - "path": "/admin/create-project-role", - "title": "Create", - "type": "protected", - }, - { - "component": [Function], - "flag": "RE", - "menu": {}, - "path": "/admin/roles/:id/edit", - "title": "Edit", - "type": "protected", - }, - { - "component": [Function], - "menu": { - "advanced": true, - "mobile": true, - }, - "parent": "/admin", - "path": "/admin/api", - "title": "API access", - "type": "protected", - }, - { - "component": [Function], - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/users", - "title": "Users", - "type": "protected", - }, - { - "component": [Function], - "flag": "serviceAccounts", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/service-accounts", - "title": "Service accounts", - "type": "protected", - }, - { - "component": [Function], - "menu": {}, - "parent": "/admin", - "path": "/admin/create-user", - "title": "Users", - "type": "protected", - }, - { - "component": [Function], - "menu": {}, - "parent": "/admin", - "path": "/admin/invite-link", - "title": "Invite link", - "type": "protected", - }, - { - "component": [Function], - "flag": "UG", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/groups", - "title": "Groups", - "type": "protected", - }, - { - "component": [Function], - "flag": "UG", - "menu": {}, - "parent": "/admin", - "path": "/admin/groups/:groupId", - "title": ":groupId", - "type": "protected", - }, - { - "component": [Function], - "flag": "UG", - "menu": {}, - "parent": "/admin/groups", - "path": "/admin/groups/create-group", - "title": "Create group", - "type": "protected", - }, - { - "component": [Function], - "flag": "UG", - "menu": {}, - "parent": "/admin/groups", - "path": "/admin/groups/:groupId/edit", - "title": "Edit group", - "type": "protected", - }, - { - "component": [Function], - "flag": "RE", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/roles", - "title": "Project roles", - "type": "protected", - }, - { - "component": [Function], - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/auth", - "title": "Single sign-on", - "type": "protected", - }, - { - "component": [Function], - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/instance", - "title": "Instance stats", - "type": "protected", - }, - { - "component": [Function], - "flag": "networkView", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/network/*", - "title": "Network", - "type": "protected", - }, - { - "component": [Function], - "flag": "maintenance", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/maintenance", - "title": "Maintenance", - "type": "protected", - }, - { - "component": [Function], - "flag": "embedProxyFrontend", - "menu": { - "adminSettings": true, - }, - "parent": "/admin", - "path": "/admin/cors", - "title": "CORS origins", - "type": "protected", - }, - { - "component": [Function], - "menu": {}, - "parent": "/admin", - "path": "/admin/billing", - "title": "Billing", - "type": "protected", - }, - { - "component": [Function], - "menu": { - "adminSettings": true, - "isEnterprise": true, - }, - "parent": "/admin", - "path": "/admin-invoices", - "title": "Billing & invoices", - "type": "protected", - }, { "component": [Function], "hidden": false, @@ -534,6 +362,21 @@ exports[`returns all baseRoutes 1`] = ` "title": "Admin", "type": "protected", }, + { + "component": { + "$$typeof": Symbol(react.lazy), + "_init": [Function], + "_payload": { + "_result": [Function], + "_status": -1, + }, + }, + "hidden": false, + "menu": {}, + "path": "/admin/*", + "title": "Admin", + "type": "protected", + }, { "component": [Function], "menu": {}, diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 6739758d40..18fe253943 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -3,33 +3,19 @@ import { StrategyView } from 'component/strategies/StrategyView/StrategyView'; import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList'; import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList'; import { AddonList } from 'component/addons/AddonList/AddonList'; -import Admin from 'component/admin'; -import AdminApi from 'component/admin/api'; -import AdminUsers from 'component/admin/users/UsersAdmin'; -import { GroupsAdmin } from 'component/admin/groups/GroupsAdmin'; -import { AuthSettings } from 'component/admin/auth/AuthSettings'; import Login from 'component/user/Login/Login'; import { EEA, P, RE, SE, UG } from 'component/common/flags'; import { NewUser } from 'component/user/NewUser/NewUser'; import ResetPassword from 'component/user/ResetPassword/ResetPassword'; import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword'; import { ProjectListNew } from 'component/project/ProjectList/ProjectList'; -import Project from 'component/project/Project/Project'; import RedirectArchive from 'component/archive/RedirectArchive'; -import { FeatureView } from 'component/feature/FeatureView/FeatureView'; -import ProjectRoles from 'component/admin/projectRoles/ProjectRoles/ProjectRoles'; -import CreateProjectRole from 'component/admin/projectRoles/CreateProjectRole/CreateProjectRole'; -import EditProjectRole from 'component/admin/projectRoles/EditProjectRole/EditProjectRole'; -import CreateUser from 'component/admin/users/CreateUser/CreateUser'; -import EditUser from 'component/admin/users/EditUser/EditUser'; -import { CreateApiToken } from 'component/admin/apiToken/CreateApiToken/CreateApiToken'; import CreateEnvironment from 'component/environments/CreateEnvironment/CreateEnvironment'; import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironment'; import { EditContext } from 'component/context/EditContext/EditContext'; import EditTagType from 'component/tags/EditTagType/EditTagType'; import CreateTagType from 'component/tags/CreateTagType/CreateTagType'; import EditProject from 'component/project/Project/EditProject/EditProject'; -import CreateProject from 'component/project/Project/CreateProject/CreateProject'; import CreateFeature from 'component/feature/CreateFeature/CreateFeature'; import EditFeature from 'component/feature/EditFeature/EditFeature'; import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit'; @@ -46,23 +32,17 @@ import { SplashPage } from 'component/splash/SplashPage/SplashPage'; import { CreateUnleashContextPage } from 'component/context/CreateUnleashContext/CreateUnleashContextPage'; import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment'; import { EditSegment } from 'component/segments/EditSegment/EditSegment'; -import { IRoute } from 'interfaces/route'; +import { INavigationMenuItem, IRoute } from 'interfaces/route'; import { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable'; import { SegmentTable } from 'component/segments/SegmentTable'; -import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect'; import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable'; -import { Billing } from 'component/admin/billing/Billing'; -import { Group } from 'component/admin/groups/Group/Group'; -import { CreateGroup } from 'component/admin/groups/CreateGroup/CreateGroup'; -import { EditGroup } from 'component/admin/groups/EditGroup/EditGroup'; import { LazyPlayground } from 'component/playground/Playground/LazyPlayground'; -import { CorsAdmin } from 'component/admin/cors'; -import { InviteLink } from 'component/admin/users/InviteLink/InviteLink'; import { Profile } from 'component/user/Profile/Profile'; -import { InstanceAdmin } from '../admin/instance-admin/InstanceAdmin'; -import { Network } from 'component/admin/network/Network'; -import { MaintenanceAdmin } from '../admin/maintenance'; -import { ServiceAccounts } from 'component/admin/serviceAccounts/ServiceAccounts'; +import { LazyCreateProject } from 'component/project/Project/CreateProject/LazyCreateProject'; +import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView'; +import { LazyAdmin } from 'component/admin/LazyAdmin'; +import { LazyProject } from 'component/project/Project/LazyProject'; +import { AdminRedirect } from 'component/admin/AdminRedirect'; export const routes: IRoute[] = [ // Splash @@ -80,7 +60,7 @@ export const routes: IRoute[] = [ path: '/projects/create', parent: '/projects', title: 'Create', - component: CreateProject, + component: LazyCreateProject, type: 'protected', enterprise: true, menu: {}, @@ -122,7 +102,7 @@ export const routes: IRoute[] = [ path: '/projects/:projectId/features/:featureId/*', parent: '/projects', title: 'FeatureView', - component: FeatureView, + component: LazyFeatureView, type: 'protected', menu: {}, }, @@ -146,7 +126,7 @@ export const routes: IRoute[] = [ path: '/projects/:projectId/*', parent: '/projects', title: ':projectId', - component: Project, + component: LazyProject, flag: P, type: 'protected', menu: {}, @@ -385,187 +365,19 @@ export const routes: IRoute[] = [ }, // Admin - { - path: '/admin/api/create-token', - parent: '/admin', - title: 'API access', - component: CreateApiToken, - type: 'protected', - menu: {}, - }, - { - path: '/admin/create-project-role', - title: 'Create', - component: CreateProjectRole, - type: 'protected', - menu: {}, - flag: RE, - }, - { - path: '/admin/roles/:id/edit', - title: 'Edit', - component: EditProjectRole, - type: 'protected', - menu: {}, - flag: RE, - }, - { - path: '/admin/users/:id/edit', - title: 'Edit', - component: EditUser, - type: 'protected', - menu: {}, - hidden: true, - }, - { - path: '/admin/api', - parent: '/admin', - title: 'API access', - component: AdminApi, - type: 'protected', - menu: { mobile: true, advanced: true }, - }, - { - path: '/admin/users', - parent: '/admin', - title: 'Users', - component: AdminUsers, - type: 'protected', - menu: { adminSettings: true }, - }, - { - path: '/admin/service-accounts', - parent: '/admin', - title: 'Service accounts', - component: ServiceAccounts, - type: 'protected', - menu: { adminSettings: true }, - flag: 'serviceAccounts', - }, - { - path: '/admin/create-user', - parent: '/admin', - title: 'Users', - component: CreateUser, - type: 'protected', - menu: {}, - }, - { - path: '/admin/invite-link', - parent: '/admin', - title: 'Invite link', - component: InviteLink, - type: 'protected', - menu: {}, - }, - { - path: '/admin/groups', - parent: '/admin', - title: 'Groups', - component: GroupsAdmin, - type: 'protected', - menu: { adminSettings: true }, - flag: UG, - }, - { - path: '/admin/groups/:groupId', - parent: '/admin', - title: ':groupId', - component: Group, - type: 'protected', - menu: {}, - flag: UG, - }, - { - path: '/admin/groups/create-group', - parent: '/admin/groups', - title: 'Create group', - component: CreateGroup, - type: 'protected', - menu: {}, - flag: UG, - }, - { - path: '/admin/groups/:groupId/edit', - parent: '/admin/groups', - title: 'Edit group', - component: EditGroup, - type: 'protected', - menu: {}, - flag: UG, - }, - { - path: '/admin/roles', - parent: '/admin', - title: 'Project roles', - component: ProjectRoles, - type: 'protected', - flag: RE, - menu: { adminSettings: true }, - }, - { - path: '/admin/auth', - parent: '/admin', - title: 'Single sign-on', - component: AuthSettings, - type: 'protected', - menu: { adminSettings: true }, - }, - { - path: '/admin/instance', - parent: '/admin', - title: 'Instance stats', - component: InstanceAdmin, - type: 'protected', - menu: { adminSettings: true }, - }, - { - path: '/admin/network/*', - parent: '/admin', - title: 'Network', - component: Network, - type: 'protected', - menu: { adminSettings: true }, - flag: 'networkView', - }, - { - path: '/admin/maintenance', - parent: '/admin', - title: 'Maintenance', - component: MaintenanceAdmin, - type: 'protected', - menu: { adminSettings: true }, - flag: 'maintenance', - }, - { - path: '/admin/cors', - parent: '/admin', - title: 'CORS origins', - component: CorsAdmin, - type: 'protected', - flag: 'embedProxyFrontend', - menu: { adminSettings: true }, - }, - { - path: '/admin/billing', - parent: '/admin', - title: 'Billing', - component: Billing, - type: 'protected', - menu: {}, - }, - { - path: '/admin-invoices', - parent: '/admin', - title: 'Billing & invoices', - component: FlaggedBillingRedirect, - type: 'protected', - menu: { adminSettings: true, isEnterprise: true }, - }, + { path: '/admin', title: 'Admin', - component: Admin, + component: AdminRedirect, + hidden: false, + type: 'protected', + menu: {}, + }, + { + path: '/admin/*', + title: 'Admin', + component: LazyAdmin, hidden: false, type: 'protected', menu: {}, @@ -620,6 +432,70 @@ export const routes: IRoute[] = [ }, ]; +export const adminMenuRoutes: INavigationMenuItem[] = [ + { + path: '/history', + title: 'Event log', + menu: { adminSettings: true }, + }, + { + path: '/admin/users', + title: 'Users', + menu: { adminSettings: true }, + }, + { + path: '/admin/groups', + title: 'Groups', + menu: { adminSettings: true }, + flag: UG, + }, + { + path: '/admin/roles', + title: 'Project roles', + flag: RE, + menu: { adminSettings: true }, + }, + { + path: '/admin/auth', + title: 'Single sign-on', + menu: { adminSettings: true }, + }, + { + path: '/admin/instance', + title: 'Instance stats', + menu: { adminSettings: true }, + }, + { + path: '/admin/service-accounts', + title: 'Service accounts', + menu: { adminSettings: true }, + flag: 'serviceAccounts', + }, + { + path: '/admin/network/*', + title: 'Network', + menu: { adminSettings: true }, + flag: 'networkView', + }, + { + path: '/admin/maintenance', + title: 'Maintenance', + menu: { adminSettings: true }, + flag: 'maintenance', + }, + { + path: '/admin/cors', + title: 'CORS origins', + flag: 'embedProxyFrontend', + menu: { adminSettings: true }, + }, + { + path: '/admin/admin-invoices', + title: 'Billing & invoices', + menu: { adminSettings: true, isEnterprise: true }, + }, +]; + export const getRoute = (path: string) => routes.find(route => route.path === path); @@ -640,4 +516,17 @@ const computeRoutes = () => { }; }; +export const getCondensedRoutes = (routes: IRoute[]): INavigationMenuItem[] => { + return routes.map(route => { + const condensedRoute = { + path: route.path, + flag: route.flag, + title: route.title, + menu: route.menu, + configFlag: route.configFlag, + }; + return condensedRoute; + }); +}; + export const getRoutes = computeRoutes(); diff --git a/frontend/src/component/project/Project/CreateProject/LazyCreateProject.tsx b/frontend/src/component/project/Project/CreateProject/LazyCreateProject.tsx new file mode 100644 index 0000000000..9bbfa07635 --- /dev/null +++ b/frontend/src/component/project/Project/CreateProject/LazyCreateProject.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const LazyCreateProject = lazy(() => import('./CreateProject')); diff --git a/frontend/src/component/project/Project/LazyProject.tsx b/frontend/src/component/project/Project/LazyProject.tsx new file mode 100644 index 0000000000..b18a87454b --- /dev/null +++ b/frontend/src/component/project/Project/LazyProject.tsx @@ -0,0 +1,3 @@ +import { lazy } from 'react'; + +export const LazyProject = lazy(() => import('./LazyProjectExport')); diff --git a/frontend/src/component/project/Project/LazyProjectExport.tsx b/frontend/src/component/project/Project/LazyProjectExport.tsx new file mode 100644 index 0000000000..e10797d06b --- /dev/null +++ b/frontend/src/component/project/Project/LazyProjectExport.tsx @@ -0,0 +1,7 @@ +// Lazy loading only supports default export. We have an ADR +// that says we should only use named exports, because +// it makes it harder to diverge from the name of the component when +// importing it in other files. +import { Project } from './Project'; + +export default Project; diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index 8844dd556c..d93e6b3a46 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -41,7 +41,7 @@ import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests import { ProjectSettings } from './ProjectSettings/ProjectSettings'; import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi'; -const Project = () => { +export const Project = () => { const projectId = useRequiredPathParam('projectId'); const params = useQueryParams(); const { project, loading, refetch } = useProject(projectId); @@ -250,5 +250,3 @@ const Project = () => {
); }; - -export default Project; diff --git a/frontend/src/interfaces/route.ts b/frontend/src/interfaces/route.ts index 504ee96dc0..0843b9527d 100644 --- a/frontend/src/interfaces/route.ts +++ b/frontend/src/interfaces/route.ts @@ -16,6 +16,14 @@ export interface IRoute { isStandalone?: boolean; } +export interface INavigationMenuItem { + path: string; + title: string; + menu: IRouteMenu; + flag?: keyof IFlags; + configFlag?: keyof IUiConfig; +} + interface IRouteMenu { mobile?: boolean; advanced?: boolean;