1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-17 01:17:29 +02:00

feat: command bar admin menu improvements (#9689)

This commit is contained in:
David Leek 2025-04-03 10:23:45 +02:00 committed by GitHub
parent 6432262be5
commit b9a7c0cda6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 103 additions and 47 deletions

View File

@ -1,16 +1,19 @@
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { adminRoutes } from './oldAdminRoutes'; import { adminRoutes as oldAdminRoutes } from './oldAdminRoutes';
import { adminRoutes } from './adminRoutes';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus'; import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
import { filterAdminRoutes } from './filterAdminRoutes'; import { filterAdminRoutes } from './filterAdminRoutes';
import { filterByConfig, mapRouteLink } from 'component/common/util'; import { filterByConfig, mapRouteLink } from 'component/common/util';
import { useUiFlag } from 'hooks/useUiFlag';
export const useAdminRoutes = () => { export const useAdminRoutes = () => {
const newAdminUIEnabled = useUiFlag('adminNavUI');
const { uiConfig, isPro, isEnterprise } = useUiConfig(); const { uiConfig, isPro, isEnterprise } = useUiConfig();
const { isBilling } = useInstanceStatus(); const { isBilling } = useInstanceStatus();
const routes = [...adminRoutes]; const routes = newAdminUIEnabled ? [...adminRoutes] : [...oldAdminRoutes];
if (uiConfig.flags.UNLEASH_CLOUD) { if (uiConfig.flags.UNLEASH_CLOUD) {
const adminBillingMenuItem = adminRoutes.findIndex( const adminBillingMenuItem = routes.findIndex(
(route) => route.title === 'Billing & invoices', (route) => route.title === 'Billing & invoices',
); );
routes[adminBillingMenuItem] = { routes[adminBillingMenuItem] = {

View File

@ -0,0 +1,22 @@
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import InsightsIcon from '@mui/icons-material/Insights';
import PlaygroundIcon from '@mui/icons-material/AutoFixNormal';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
export const ButtonItemIcon = ({
path,
}: {
path: string;
}) => {
if (path === '/projects') {
return <ProjectIcon />;
}
if (path === '/playground') {
return <PlaygroundIcon />;
}
if (path === '/insights') {
return <InsightsIcon />;
}
return <IconRenderer path={path} />;
};

View File

@ -18,7 +18,6 @@ import {
type CommandResultGroupItem, type CommandResultGroupItem,
} from './RecentlyVisited/CommandResultGroup'; } from './RecentlyVisited/CommandResultGroup';
import { CommandPageSuggestions } from './CommandPageSuggestions'; import { CommandPageSuggestions } from './CommandPageSuggestions';
import { useRoutes } from 'component/layout/MainLayout/NavigationSidebar/useRoutes';
import { useAsyncDebounce } from 'react-table'; import { useAsyncDebounce } from 'react-table';
import useProjects from 'hooks/api/getters/useProjects/useProjects'; import useProjects from 'hooks/api/getters/useProjects/useProjects';
import { import {
@ -31,6 +30,7 @@ import { CommandSearchPages } from './CommandSearchPages';
import { CommandBarFeedback } from './CommandBarFeedback'; import { CommandBarFeedback } from './CommandBarFeedback';
import { RecentlyVisitedRecorder } from './RecentlyVisitedRecorder'; import { RecentlyVisitedRecorder } from './RecentlyVisitedRecorder';
import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly'; import { ScreenReaderOnly } from 'component/common/ScreenReaderOnly/ScreenReaderOnly';
import { useCommandBarRoutes } from './useCommandBarRoutes';
export const CommandResultsPaper = styled(Paper)(({ theme }) => ({ export const CommandResultsPaper = styled(Paper)(({ theme }) => ({
position: 'absolute', position: 'absolute',
@ -101,12 +101,6 @@ const StyledClose = styled(Close)(({ theme }) => ({
fontSize: theme.typography.body1.fontSize, fontSize: theme.typography.body1.fontSize,
})); }));
interface IPageRouteInfo {
path: string;
route: string;
title: string;
}
export const CommandBar = () => { export const CommandBar = () => {
const { trackEvent } = usePlausibleTracker(); const { trackEvent } = usePlausibleTracker();
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
@ -124,19 +118,7 @@ export const CommandBar = () => {
useState<CommandQueryCounter>({ query: '', count: 0 }); useState<CommandQueryCounter>({ query: '', count: 0 });
const [hasNoResults, setHasNoResults] = useState(false); const [hasNoResults, setHasNoResults] = useState(false);
const [value, setValue] = useState<string>(''); const [value, setValue] = useState<string>('');
const { routes } = useRoutes(); const { allRoutes } = useCommandBarRoutes();
const allRoutes: Record<string, IPageRouteInfo> = {};
for (const route of [
...routes.mainNavRoutes,
...routes.adminRoutes,
...routes.primaryRoutes,
]) {
allRoutes[route.path] = {
path: route.path,
route: route.route,
title: route.title,
};
}
const hideSuggestions = () => { const hideSuggestions = () => {
setShowSuggestions(false); setShowSuggestions(false);
@ -159,7 +141,7 @@ export const CommandBar = () => {
setSearchedProjects(mappedProjects); setSearchedProjects(mappedProjects);
const filteredPages = Object.values(allRoutes).filter((route) => const filteredPages = Object.values(allRoutes).filter((route) =>
route.title.toLowerCase().includes(query.toLowerCase()), route.searchText.toLowerCase().includes(query.toLowerCase()),
); );
const mappedPages = filteredPages.map((page) => ({ const mappedPages = filteredPages.map((page) => ({
name: page.title, name: page.title,

View File

@ -9,7 +9,7 @@ import {
type CommandResultGroupItem, type CommandResultGroupItem,
} from './RecentlyVisited/CommandResultGroup'; } from './RecentlyVisited/CommandResultGroup';
import { ListItemButton } from '@mui/material'; import { ListItemButton } from '@mui/material';
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer'; import { ButtonItemIcon } from './ButtonItemIcon';
export const CommandSearchPages = ({ export const CommandSearchPages = ({
items, items,
@ -50,7 +50,7 @@ export const CommandSearchPages = ({
sx={listItemButtonStyle} sx={listItemButtonStyle}
> >
<StyledListItemIcon> <StyledListItemIcon>
<IconRenderer path={item.link} /> <ButtonItemIcon path={item.link} />
</StyledListItemIcon> </StyledListItemIcon>
<StyledListItemText> <StyledListItemText>
<StyledButtonTypography color='textPrimary'> <StyledButtonTypography color='textPrimary'>

View File

@ -10,14 +10,12 @@ import {
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { Theme } from '@mui/material/styles/createTheme'; import type { Theme } from '@mui/material/styles/createTheme';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IconRenderer } from 'component/layout/MainLayout/NavigationSidebar/IconRenderer';
import InsightsIcon from '@mui/icons-material/Insights';
import PlaygroundIcon from '@mui/icons-material/AutoFixNormal';
import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver'; import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import { Children } from 'react'; import { Children } from 'react';
import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon'; import { ProjectIcon } from 'component/common/ProjectIcon/ProjectIcon';
import { ButtonItemIcon } from '../ButtonItemIcon';
export const listItemButtonStyle = (theme: Theme) => ({ export const listItemButtonStyle = (theme: Theme) => ({
border: `1px solid transparent`, border: `1px solid transparent`,
@ -48,24 +46,6 @@ export interface CommandResultGroupItem {
description?: string | null; description?: string | null;
} }
const ButtonItemIcon = ({
path,
}: {
path: string;
}) => {
if (path === '/projects') {
return <ProjectIcon />;
}
if (path === '/playground') {
return <PlaygroundIcon />;
}
if (path === '/insights') {
return <InsightsIcon />;
}
return <IconRenderer path={path} />;
};
export const RecentlyVisitedPathButton = ({ export const RecentlyVisitedPathButton = ({
keyName, keyName,
path, path,

View File

@ -0,0 +1,55 @@
import { adminGroups } from 'component/admin/adminRoutes';
import { useRoutes } from 'component/layout/MainLayout/NavigationSidebar/useRoutes';
import { useUiFlag } from 'hooks/useUiFlag';
import type { INavigationMenuItem } from 'interfaces/route';
import { useMemo } from 'react';
interface IPageRouteInfo {
path: string;
route: string;
title: string;
searchText: string;
}
export const useCommandBarRoutes = () => {
const newAdminUIEnabled = useUiFlag('adminNavUI');
const { routes } = useRoutes();
const getSearchText = (route: INavigationMenuItem, title: string) => {
if (route.group && adminGroups[route.group]) {
return `${title} ${route.path} ${route.group} ${adminGroups[route.group]}`;
}
return `${title} ${route.path}`;
};
const getRouteTitle = (route: INavigationMenuItem) => {
if (route.path === '/admin') {
return 'Admin settings';
}
return route.title;
};
return useMemo(() => {
const allRoutes: Record<string, IPageRouteInfo> = {};
for (const route of [
...routes.mainNavRoutes,
...routes.adminRoutes,
...routes.primaryRoutes,
]) {
const title = getRouteTitle(route);
allRoutes[route.path] = {
path: route.path,
route: route.route,
title: title,
searchText: newAdminUIEnabled
? getSearchText(route, title)
: title,
};
}
return {
allRoutes,
newAdminUIEnabled,
};
}, [routes]);
};

View File

@ -13,7 +13,9 @@ import UsersIcon from '@mui/icons-material/GroupOutlined';
import ServiceAccountIcon from '@mui/icons-material/Computer'; import ServiceAccountIcon from '@mui/icons-material/Computer';
import GroupsIcon from '@mui/icons-material/GroupsOutlined'; import GroupsIcon from '@mui/icons-material/GroupsOutlined';
import RoleIcon from '@mui/icons-material/AdminPanelSettingsOutlined'; import RoleIcon from '@mui/icons-material/AdminPanelSettingsOutlined';
import SettingsIcon from '@mui/icons-material/Settings';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
import InsightsIcon from '@mui/icons-material/Insights';
import ApiAccessIcon from '@mui/icons-material/KeyOutlined'; import ApiAccessIcon from '@mui/icons-material/KeyOutlined';
import SingleSignOnIcon from '@mui/icons-material/AssignmentOutlined'; import SingleSignOnIcon from '@mui/icons-material/AssignmentOutlined';
import NetworkIcon from '@mui/icons-material/HubOutlined'; import NetworkIcon from '@mui/icons-material/HubOutlined';
@ -39,6 +41,7 @@ const icons: Record<
typeof SvgIcon | FC<ComponentProps<typeof SvgIcon>> typeof SvgIcon | FC<ComponentProps<typeof SvgIcon>>
> = { > = {
'/search': SearchIcon, '/search': SearchIcon,
'/insights': InsightsIcon,
'/applications': ApplicationsIcon, '/applications': ApplicationsIcon,
'/context': ContextFieldsIcon, '/context': ContextFieldsIcon,
'/feature-toggle-type': FlagTypesIcon, '/feature-toggle-type': FlagTypesIcon,
@ -47,13 +50,24 @@ const icons: Record<
'/strategies': CustomStrategiesIcon, '/strategies': CustomStrategiesIcon,
'/tag-types': TagTypesIcon, '/tag-types': TagTypesIcon,
'/environments': EnvironmentsIcon, '/environments': EnvironmentsIcon,
'/admin': SettingsIcon,
'/admin/users': UsersIcon, '/admin/users': UsersIcon,
'/admin/service-accounts': ServiceAccountIcon, '/admin/service-accounts': ServiceAccountIcon,
'/admin/groups': GroupsIcon, '/admin/groups': GroupsIcon,
'/admin/roles': RoleIcon, '/admin/roles': RoleIcon,
'/admin/roles/project-roles': RoleIcon,
'/admin/api': ApiAccessIcon, '/admin/api': ApiAccessIcon,
'/admin/auth': SingleSignOnIcon, '/admin/auth': SingleSignOnIcon,
'/admin/auth/saml': SingleSignOnIcon,
'/admin/auth/scim': SingleSignOnIcon,
'/admin/auth/password': SingleSignOnIcon,
'/admin/auth/google': SingleSignOnIcon,
'/admin/network': NetworkIcon, '/admin/network': NetworkIcon,
'/admin/network/traffic': NetworkIcon,
'/admin/network/data-usage': NetworkIcon,
'/admin/network/frontend-data-usage': NetworkIcon,
'/admin/network/connected-edges': NetworkIcon,
'/admin/network/backend-connections': NetworkIcon,
'/admin/maintenance': MaintenanceIcon, '/admin/maintenance': MaintenanceIcon,
'/admin/banners': BannersIcon, '/admin/banners': BannersIcon,
'/admin/instance': InstanceStatsIcon, '/admin/instance': InstanceStatsIcon,