1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

Refactor/lazy load (#2842)

Currently our bundle size is clocking in at: 1,798.28 kB │ gzip: 558.42 kB
After the changes: 1,299.32 kB │ gzip: 403.26 kB
This commit is contained in:
Fredrik Strand Oseberg 2023-01-12 11:34:45 +01:00 committed by GitHub
parent ce815e5f29
commit 0dcf28a0f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 279 additions and 533 deletions

View File

@ -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`. 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 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. 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

View File

@ -14,7 +14,7 @@
"start": "vite", "start": "vite",
"start:prod": "vite build && vite preview", "start:prod": "vite build && vite preview",
"start:sandbox": "UNLEASH_API=https://sandbox.getunleash.io/ospro yarn run start", "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", "start:demo": "UNLEASH_BASE_PATH=/demo/ yarn start",
"test": "tsc && vitest run", "test": "tsc && vitest run",
"test:snapshot": "yarn test -u", "test:snapshot": "yarn test -u",

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -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 = () => (
<>
<AdminMenu />
<Routes>
<Route path="users" element={<UsersAdmin />} />
<Route path="create-project-role" element={<CreateProjectRole />} />
<Route path="roles/:id/edit" element={<EditProjectRole />} />
<Route path="api" element={<ApiPage />} />
<Route path="api/create-token" element={<CreateApiToken />} />
<Route path="users/:id/edit" element={<EditUser />} />
<Route path="service-accounts" element={<ServiceAccounts />} />
<Route path="create-user" element={<CreateUser />} />
<Route path="invite-link" element={<InviteLink />} />
<Route path="groups" element={<GroupsAdmin />} />
<Route path="groups/create-group" element={<CreateGroup />} />
<Route path="groups/:groupId/edit" element={<EditGroup />} />
<Route path="groups/:groupId" element={<Group />} />
<Route path="roles" element={<ProjectRoles />} />
<Route path="instance" element={<InstanceAdmin />} />
<Route path="network/*" element={<Network />} />
<Route path="maintenance" element={<MaintenanceAdmin />} />
<Route path="cors" element={<CorsAdmin />} />
<Route path="auth" element={<AuthSettings />} />
<Route path="admin-invoices" element={<FlaggedBillingRedirect />} />
<Route path="billing" element={<Billing />} />
</Routes>
</>
);

View File

@ -0,0 +1,3 @@
import { Navigate } from 'react-router-dom';
export const AdminRedirect = () => <Navigate to="/admin/users" replace />;

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LazyAdmin = lazy(() => import('./LazyAdminExport'));

View File

@ -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;

View File

@ -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 { 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 ( return (
<div> <div>
<ConditionallyRender
condition={showAdminMenu}
show={<AdminMenu />}
/>
<ApiTokenPage /> <ApiTokenPage />
</div> </div>
); );

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import AdminMenu from '../menu/AdminMenu';
import { Alert } from '@mui/material'; import { Alert } from '@mui/material';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
@ -34,7 +33,6 @@ export const AuthSettings = () => {
return ( return (
<div> <div>
<AdminMenu />
<PageContent header="Single Sign-On"> <PageContent header="Single Sign-On">
<ConditionallyRender <ConditionallyRender
condition={authenticationType === 'enterprise'} condition={authenticationType === 'enterprise'}

View File

@ -1,4 +1,3 @@
import AdminMenu from '../menu/AdminMenu';
import { PageContent } from 'component/common/PageContent/PageContent'; import { PageContent } from 'component/common/PageContent/PageContent';
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
@ -32,7 +31,6 @@ export const Billing = () => {
return ( return (
<div> <div>
<AdminMenu />
<PageContent header="Billing" isLoading={loading}> <PageContent header="Billing" isLoading={loading}>
<ConditionallyRender <ConditionallyRender
condition={isBilling} condition={isBilling}

View File

@ -1,6 +1,5 @@
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AdminMenu from '../menu/AdminMenu';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
@ -13,16 +12,10 @@ import { CorsForm } from 'component/admin/cors/CorsForm';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
export const CorsAdmin = () => { export const CorsAdmin = () => {
const { pathname } = useLocation();
const showAdminMenu = pathname.includes('/admin/');
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
return ( return (
<div> <div>
<ConditionallyRender
condition={showAdminMenu}
show={<AdminMenu />}
/>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}
show={<CorsPage />} show={<CorsPage />}

View File

@ -1,10 +1,8 @@
import { GroupsList } from './GroupsList/GroupsList'; import { GroupsList } from './GroupsList/GroupsList';
import AdminMenu from '../menu/AdminMenu';
export const GroupsAdmin = () => { export const GroupsAdmin = () => {
return ( return (
<div> <div>
<AdminMenu />
<GroupsList /> <GroupsList />
</div> </div>
); );

View File

@ -1,12 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Navigate } from 'react-router-dom';
const render = () => <Navigate to="/admin/users" replace />;
render.propTypes = {
match: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
};
export default render;

View File

@ -1,10 +1,8 @@
import AdminMenu from '../menu/AdminMenu';
import { InstanceStats } from './InstanceStats/InstanceStats'; import { InstanceStats } from './InstanceStats/InstanceStats';
export const InstanceAdmin = () => { export const InstanceAdmin = () => {
return ( return (
<div> <div>
<AdminMenu />
<InstanceStats /> <InstanceStats />
</div> </div>
); );

View File

@ -1,6 +1,4 @@
import { useLocation } from 'react-router-dom';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import AdminMenu from '../menu/AdminMenu';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
@ -13,16 +11,10 @@ import { MaintenanceTooltip } from './MaintenanceTooltip';
import { MaintenanceToggle } from './MaintenanceToggle'; import { MaintenanceToggle } from './MaintenanceToggle';
export const MaintenanceAdmin = () => { export const MaintenanceAdmin = () => {
const { pathname } = useLocation();
const showAdminMenu = pathname.includes('/admin/');
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
return ( return (
<div> <div>
<ConditionallyRender
condition={showAdminMenu}
show={<AdminMenu />}
/>
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}
show={<MaintenancePage />} show={<MaintenancePage />}

View File

@ -1,5 +1,5 @@
import { lazy } from 'react'; import { lazy } from 'react';
import AdminMenu from '../menu/AdminMenu';
import { styled, Tab, Tabs } from '@mui/material'; import { styled, Tab, Tabs } from '@mui/material';
import { Route, Routes, useLocation } from 'react-router-dom'; import { Route, Routes, useLocation } from 'react-router-dom';
import { CenteredNavLink } from '../menu/CenteredNavLink'; import { CenteredNavLink } from '../menu/CenteredNavLink';
@ -30,7 +30,6 @@ export const Network = () => {
return ( return (
<div> <div>
<AdminMenu />
<StyledPageContent <StyledPageContent
headerClass="page-header" headerClass="page-header"
header={ header={

View File

@ -2,7 +2,6 @@ import { useContext } from 'react';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import AdminMenu from 'component/admin/menu/AdminMenu';
import ProjectRoleList from './ProjectRoleList/ProjectRoleList'; import ProjectRoleList from './ProjectRoleList/ProjectRoleList';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
@ -11,7 +10,6 @@ const ProjectRoles = () => {
return ( return (
<div> <div>
<AdminMenu />
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}
show={<ProjectRoleList />} show={<ProjectRoleList />}

View File

@ -1,5 +1,4 @@
import { useContext } from 'react'; import { useContext } from 'react';
import AdminMenu from '../menu/AdminMenu';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
@ -11,7 +10,6 @@ export const ServiceAccounts = () => {
return ( return (
<div> <div>
<AdminMenu />
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}
show={<ServiceAccountsTable />} show={<ServiceAccountsTable />}

View File

@ -1,18 +1,16 @@
import { useContext } from 'react'; import { useContext } from 'react';
import UsersList from './UsersList/UsersList'; import UsersList from './UsersList/UsersList';
import AdminMenu from '../menu/AdminMenu';
import AccessContext from 'contexts/AccessContext'; import AccessContext from 'contexts/AccessContext';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; import { AdminAlert } from 'component/common/AdminAlert/AdminAlert';
import { InviteLinkBar } from './InviteLinkBar/InviteLinkBar'; import { InviteLinkBar } from './InviteLinkBar/InviteLinkBar';
const UsersAdmin = () => { export const UsersAdmin = () => {
const { hasAccess } = useContext(AccessContext); const { hasAccess } = useContext(AccessContext);
return ( return (
<div> <div>
<AdminMenu />
<InviteLinkBar /> <InviteLinkBar />
<ConditionallyRender <ConditionallyRender
condition={hasAccess(ADMIN)} condition={hasAccess(ADMIN)}

View File

@ -1,10 +1,11 @@
import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums'; import { weightTypes } from '../feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums';
import { IUiConfig } from 'interfaces/uiConfig'; import { IUiConfig } from 'interfaces/uiConfig';
import { IRoute } from 'interfaces/route'; import { INavigationMenuItem } from 'interfaces/route';
import { IFeatureVariant } from 'interfaces/featureToggle'; import { IFeatureVariant } from 'interfaces/featureToggle';
import { format, isValid } from 'date-fns'; import { format, isValid } from 'date-fns';
export const filterByConfig = (config: IUiConfig) => (r: IRoute) => { export const filterByConfig =
(config: IUiConfig) => (r: INavigationMenuItem) => {
if (r.flag) { if (r.flag) {
// Check if the route's `flag` is enabled in IUiConfig.flags. // Check if the route's `flag` is enabled in IUiConfig.flags.
const flags = config.flags as unknown as Record<string, boolean>; const flags = config.flags as unknown as Record<string, boolean>;

View File

@ -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;

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LazyFeatureView = lazy(() => import('./FeatureViewLazyExport'));

View File

@ -9,7 +9,7 @@ import NavigationLink from '../NavigationLink/NavigationLink';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { basePath } from 'utils/formatPath'; import { basePath } from 'utils/formatPath';
import { IFlags } from 'interfaces/uiConfig'; import { IFlags } from 'interfaces/uiConfig';
import { IRoute } from 'interfaces/route'; import { INavigationMenuItem } from 'interfaces/route';
import styles from './DrawerMenu.module.scss'; // FIXME: useStyle - theme import styles from './DrawerMenu.module.scss'; // FIXME: useStyle - theme
interface IDrawerMenuProps { interface IDrawerMenuProps {
@ -25,9 +25,9 @@ interface IDrawerMenuProps {
}>; }>;
flags?: IFlags; flags?: IFlags;
routes: { routes: {
mainNavRoutes: IRoute[]; mainNavRoutes: INavigationMenuItem[];
mobileRoutes: IRoute[]; mobileRoutes: INavigationMenuItem[];
adminRoutes: IRoute[]; adminRoutes: INavigationMenuItem[];
}; };
} }

View File

@ -26,12 +26,16 @@ import { flexRow, focusable } from 'themes/themeStyles';
import { ADMIN } from 'component/providers/AccessProvider/permissions'; import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { IPermission } from 'interfaces/user'; import { IPermission } from 'interfaces/user';
import { NavigationMenu } from './NavigationMenu/NavigationMenu'; 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 { KeyboardArrowDown } from '@mui/icons-material';
import { filterByConfig } from 'component/common/util'; import { filterByConfig } from 'component/common/util';
import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions'; import { useAuthPermissions } from 'hooks/api/getters/useAuth/useAuthPermissions';
import { useId } from 'hooks/useId'; import { useId } from 'hooks/useId';
import { IRoute } from 'interfaces/route'; import { INavigationMenuItem } from 'interfaces/route';
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode'; import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
import { useThemeMode } from 'hooks/useThemeMode'; import { useThemeMode } from 'hooks/useThemeMode';
@ -134,14 +138,24 @@ const Header: VFC = () => {
const routes = getRoutes(); const routes = getRoutes();
const filterByEnterprise = (route: IRoute): boolean => { const filterByEnterprise = (route: INavigationMenuItem): boolean => {
return !route.menu.isEnterprise || !isOss(); return !route.menu.isEnterprise || !isOss();
}; };
const filteredMainRoutes = { const filteredMainRoutes = {
mainNavRoutes: routes.mainNavRoutes.filter(filterByConfig(uiConfig)), mainNavRoutes: getCondensedRoutes(routes.mainNavRoutes)
mobileRoutes: routes.mobileRoutes.filter(filterByConfig(uiConfig)), .concat([
adminRoutes: routes.adminRoutes {
path: '/admin/api',
title: 'API access',
menu: {},
},
])
.filter(filterByConfig(uiConfig)),
mobileRoutes: getCondensedRoutes(routes.mobileRoutes).filter(
filterByConfig(uiConfig)
),
adminRoutes: adminMenuRoutes
.filter(filterByConfig(uiConfig)) .filter(filterByConfig(uiConfig))
.filter(filterByEnterprise) .filter(filterByEnterprise)
.map(route => ({ .map(route => ({

View File

@ -11,7 +11,14 @@ exports[`returns all baseRoutes 1`] = `
"type": "protected", "type": "protected",
}, },
{ {
"component": [Function], "component": {
"$$typeof": Symbol(react.lazy),
"_init": [Function],
"_payload": {
"_result": [Function],
"_status": -1,
},
},
"enterprise": true, "enterprise": true,
"menu": {}, "menu": {},
"parent": "/projects", "parent": "/projects",
@ -53,7 +60,14 @@ exports[`returns all baseRoutes 1`] = `
"type": "protected", "type": "protected",
}, },
{ {
"component": [Function], "component": {
"$$typeof": Symbol(react.lazy),
"_init": [Function],
"_payload": {
"_result": [Function],
"_status": -1,
},
},
"menu": {}, "menu": {},
"parent": "/projects", "parent": "/projects",
"path": "/projects/:projectId/features/:featureId/*", "path": "/projects/:projectId/features/:featureId/*",
@ -77,7 +91,14 @@ exports[`returns all baseRoutes 1`] = `
"type": "protected", "type": "protected",
}, },
{ {
"component": [Function], "component": {
"$$typeof": Symbol(react.lazy),
"_init": [Function],
"_payload": {
"_result": [Function],
"_status": -1,
},
},
"flag": "P", "flag": "P",
"menu": {}, "menu": {},
"parent": "/projects", "parent": "/projects",
@ -333,199 +354,6 @@ exports[`returns all baseRoutes 1`] = `
"title": "Archived toggles", "title": "Archived toggles",
"type": "protected", "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], "component": [Function],
"hidden": false, "hidden": false,
@ -534,6 +362,21 @@ exports[`returns all baseRoutes 1`] = `
"title": "Admin", "title": "Admin",
"type": "protected", "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], "component": [Function],
"menu": {}, "menu": {},

View File

@ -3,33 +3,19 @@ import { StrategyView } from 'component/strategies/StrategyView/StrategyView';
import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList'; import { StrategiesList } from 'component/strategies/StrategiesList/StrategiesList';
import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList'; import { TagTypeList } from 'component/tags/TagTypeList/TagTypeList';
import { AddonList } from 'component/addons/AddonList/AddonList'; 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 Login from 'component/user/Login/Login';
import { EEA, P, RE, SE, UG } from 'component/common/flags'; import { EEA, P, RE, SE, UG } from 'component/common/flags';
import { NewUser } from 'component/user/NewUser/NewUser'; import { NewUser } from 'component/user/NewUser/NewUser';
import ResetPassword from 'component/user/ResetPassword/ResetPassword'; import ResetPassword from 'component/user/ResetPassword/ResetPassword';
import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword'; import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword';
import { ProjectListNew } from 'component/project/ProjectList/ProjectList'; import { ProjectListNew } from 'component/project/ProjectList/ProjectList';
import Project from 'component/project/Project/Project';
import RedirectArchive from 'component/archive/RedirectArchive'; 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 CreateEnvironment from 'component/environments/CreateEnvironment/CreateEnvironment';
import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironment'; import EditEnvironment from 'component/environments/EditEnvironment/EditEnvironment';
import { EditContext } from 'component/context/EditContext/EditContext'; import { EditContext } from 'component/context/EditContext/EditContext';
import EditTagType from 'component/tags/EditTagType/EditTagType'; import EditTagType from 'component/tags/EditTagType/EditTagType';
import CreateTagType from 'component/tags/CreateTagType/CreateTagType'; import CreateTagType from 'component/tags/CreateTagType/CreateTagType';
import EditProject from 'component/project/Project/EditProject/EditProject'; import EditProject from 'component/project/Project/EditProject/EditProject';
import CreateProject from 'component/project/Project/CreateProject/CreateProject';
import CreateFeature from 'component/feature/CreateFeature/CreateFeature'; import CreateFeature from 'component/feature/CreateFeature/CreateFeature';
import EditFeature from 'component/feature/EditFeature/EditFeature'; import EditFeature from 'component/feature/EditFeature/EditFeature';
import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit'; 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 { CreateUnleashContextPage } from 'component/context/CreateUnleashContext/CreateUnleashContextPage';
import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment'; import { CreateSegment } from 'component/segments/CreateSegment/CreateSegment';
import { EditSegment } from 'component/segments/EditSegment/EditSegment'; 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 { EnvironmentTable } from 'component/environments/EnvironmentTable/EnvironmentTable';
import { SegmentTable } from 'component/segments/SegmentTable'; import { SegmentTable } from 'component/segments/SegmentTable';
import FlaggedBillingRedirect from 'component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect';
import { FeaturesArchiveTable } from '../archive/FeaturesArchiveTable'; 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 { 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 { Profile } from 'component/user/Profile/Profile';
import { InstanceAdmin } from '../admin/instance-admin/InstanceAdmin'; import { LazyCreateProject } from 'component/project/Project/CreateProject/LazyCreateProject';
import { Network } from 'component/admin/network/Network'; import { LazyFeatureView } from 'component/feature/FeatureView/LazyFeatureView';
import { MaintenanceAdmin } from '../admin/maintenance'; import { LazyAdmin } from 'component/admin/LazyAdmin';
import { ServiceAccounts } from 'component/admin/serviceAccounts/ServiceAccounts'; import { LazyProject } from 'component/project/Project/LazyProject';
import { AdminRedirect } from 'component/admin/AdminRedirect';
export const routes: IRoute[] = [ export const routes: IRoute[] = [
// Splash // Splash
@ -80,7 +60,7 @@ export const routes: IRoute[] = [
path: '/projects/create', path: '/projects/create',
parent: '/projects', parent: '/projects',
title: 'Create', title: 'Create',
component: CreateProject, component: LazyCreateProject,
type: 'protected', type: 'protected',
enterprise: true, enterprise: true,
menu: {}, menu: {},
@ -122,7 +102,7 @@ export const routes: IRoute[] = [
path: '/projects/:projectId/features/:featureId/*', path: '/projects/:projectId/features/:featureId/*',
parent: '/projects', parent: '/projects',
title: 'FeatureView', title: 'FeatureView',
component: FeatureView, component: LazyFeatureView,
type: 'protected', type: 'protected',
menu: {}, menu: {},
}, },
@ -146,7 +126,7 @@ export const routes: IRoute[] = [
path: '/projects/:projectId/*', path: '/projects/:projectId/*',
parent: '/projects', parent: '/projects',
title: ':projectId', title: ':projectId',
component: Project, component: LazyProject,
flag: P, flag: P,
type: 'protected', type: 'protected',
menu: {}, menu: {},
@ -385,187 +365,19 @@ export const routes: IRoute[] = [
}, },
// Admin // 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', path: '/admin',
title: 'Admin', title: 'Admin',
component: Admin, component: AdminRedirect,
hidden: false,
type: 'protected',
menu: {},
},
{
path: '/admin/*',
title: 'Admin',
component: LazyAdmin,
hidden: false, hidden: false,
type: 'protected', type: 'protected',
menu: {}, 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) => export const getRoute = (path: string) =>
routes.find(route => route.path === path); 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(); export const getRoutes = computeRoutes();

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LazyCreateProject = lazy(() => import('./CreateProject'));

View File

@ -0,0 +1,3 @@
import { lazy } from 'react';
export const LazyProject = lazy(() => import('./LazyProjectExport'));

View File

@ -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;

View File

@ -41,7 +41,7 @@ import { ProjectChangeRequests } from '../../changeRequest/ProjectChangeRequests
import { ProjectSettings } from './ProjectSettings/ProjectSettings'; import { ProjectSettings } from './ProjectSettings/ProjectSettings';
import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi'; import { useFavoriteProjectsApi } from 'hooks/api/actions/useFavoriteProjectsApi/useFavoriteProjectsApi';
const Project = () => { export const Project = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const params = useQueryParams(); const params = useQueryParams();
const { project, loading, refetch } = useProject(projectId); const { project, loading, refetch } = useProject(projectId);
@ -250,5 +250,3 @@ const Project = () => {
</div> </div>
); );
}; };
export default Project;

View File

@ -16,6 +16,14 @@ export interface IRoute {
isStandalone?: boolean; isStandalone?: boolean;
} }
export interface INavigationMenuItem {
path: string;
title: string;
menu: IRouteMenu;
flag?: keyof IFlags;
configFlag?: keyof IUiConfig;
}
interface IRouteMenu { interface IRouteMenu {
mobile?: boolean; mobile?: boolean;
advanced?: boolean; advanced?: boolean;