CommandPalette: Add actions for entire Nav tree (#58138)

* wip for exposing full nav tree in command palatte

* Expose whole nav tree in command palette

* give search an icon

* comments

* remove unused index variable

* navigate to parents

* include image icons

* comment
This commit is contained in:
Josh Hunt 2022-11-30 17:34:25 +00:00 committed by GitHub
parent b12b5ed92f
commit 93eb8cb51d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 129 additions and 231 deletions

View File

@ -34,6 +34,13 @@ export const ResultItem = React.forwardRef(
const theme = useTheme2();
const styles = getResultItemStyles(theme, active);
let name = action.name;
// TODO: does this needs adjusting for i18n?
if (action.children && !action.command?.perform && !name.endsWith('...')) {
name += '...';
}
return (
<div ref={ref} className={styles.row}>
<div className={styles.actionContainer}>
@ -47,7 +54,7 @@ export const ResultItem = React.forwardRef(
<span className={styles.breadcrumbAncestor}>&rsaquo;</span>
</React.Fragment>
))}
<span>{action.name}</span>
<span>{name}</span>
</div>
</div>
{action.subtitle && <span className={styles.subtitleText}>{action.subtitle}</span>}
@ -94,9 +101,9 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => {
}),
actionContainer: css({
display: 'flex',
gap: theme.spacing(2),
alignitems: 'center',
fontsize: theme.typography.fontSize,
gap: theme.spacing(1),
alignItems: 'center',
fontSize: theme.typography.fontSize,
}),
textContainer: css({
display: 'flex',
@ -106,7 +113,7 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => {
padding: theme.spacing(0, 1),
background: shortcutBackgroundColor,
borderRadius: theme.shape.borderRadius(),
fontsize: theme.typography.fontSize,
fontSize: theme.typography.fontSize,
}),
breadcrumbAncestor: css({
opacity: 0.5,

View File

@ -1,73 +0,0 @@
import { Priority } from 'kbar';
import { locationService } from '@grafana/runtime';
import { NavBarActions } from './global.static.actions';
// Grafana Alerting and alerting sub navigation items
const alertingCommandPaletteStaticActions: NavBarActions[] = [
{
url: '/alerting/list',
actions: [
{
id: 'go/alerting',
name: 'Go to alerting',
keywords: 'alerting navigate',
perform: () => locationService.push('/alerting'),
section: 'Navigation',
priority: Priority.NORMAL,
},
],
},
{
url: '/alerting/list',
actions: [
{
id: 'go/alerting/rules',
name: 'Go to alert rules',
keywords: 'alerting navigate rules',
perform: () => locationService.push('/alerting/list'),
section: 'Navigation',
parent: 'go/alerting',
},
],
},
{
url: '/alerting/notifications',
actions: [
{
id: 'go/alerting/contact-points',
name: 'Go to contact points',
keywords: 'alerting navigate contact-points',
perform: () => locationService.push('/alerting/notifications'),
parent: 'go/alerting',
},
],
},
{
url: '/alerting/routes',
actions: [
{
id: 'go/alerting/notification-policies',
name: 'Go to notification policies',
keywords: 'alerting navigate notification-policies',
perform: () => locationService.push('/alerting/routes'),
parent: 'go/alerting',
},
],
},
{
url: '/alerting/silences',
actions: [
{
id: 'go/alerting/silences',
name: 'Go to silences',
keywords: 'alerting navigate silences',
perform: () => locationService.push('/alerting/silences'),
parent: 'go/alerting',
},
],
},
];
export { alertingCommandPaletteStaticActions };

View File

@ -1,153 +0,0 @@
import { Action, Priority } from 'kbar';
import { flatMapDeep } from 'lodash';
import { NavModelItem } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { alertingCommandPaletteStaticActions } from './alerting.static.actions';
export interface NavBarActions {
url: string;
actions: Action[];
}
export default (navBarTree: NavModelItem[]) => {
const globalActions: Action[] = [
{
id: 'go/search',
name: 'Go to dashboard search',
keywords: 'navigate',
perform: () => locationService.push('?search=open'),
section: 'Navigation',
shortcut: ['s', 'o'],
},
{
id: 'go/dashboard',
name: 'Go to dashboard...',
keywords: 'navigate',
section: 'Navigation',
priority: Priority.NORMAL,
},
{
id: 'preferences/theme',
name: 'Change theme...',
keywords: 'interface color dark light',
section: 'Preferences',
shortcut: ['c', 't'],
},
{
id: 'preferences/dark-theme',
name: 'Dark',
keywords: 'dark theme',
section: '',
perform: () => {
locationService.push({ search: '?theme=dark' });
location.reload();
},
parent: 'preferences/theme',
},
{
id: 'preferences/light-theme',
name: 'Light',
keywords: 'light theme',
section: '',
perform: () => {
locationService.push({ search: '?theme=light' });
location.reload();
},
parent: 'preferences/theme',
},
];
// this maps actions to navbar by URL items for showing/hiding
// actions is an array for multiple child actions that would be under one navbar item
const navBarActionMap: NavBarActions[] = [
{
url: '/dashboard/new',
actions: [
{
id: 'management/create-folder',
name: 'Create folder',
keywords: 'new add',
perform: () => locationService.push('/dashboards/folder/new'),
section: 'Management',
},
{
id: 'management/create-dashboard',
name: 'Create dashboard',
keywords: 'new add',
perform: () => locationService.push('/dashboard/new'),
section: 'Management',
},
],
},
{
url: '/',
actions: [
{
id: 'go/home',
name: 'Go to home',
keywords: 'navigate',
perform: () => locationService.push('/'),
section: 'Navigation',
shortcut: ['g', 'h'],
priority: Priority.HIGH,
},
],
},
{
url: '/explore',
actions: [
{
id: 'go/explore',
name: 'Go to explore',
keywords: 'navigate',
perform: () => locationService.push('/explore'),
section: 'Navigation',
priority: Priority.NORMAL,
},
],
},
...alertingCommandPaletteStaticActions,
{
url: '/profile',
actions: [
{
id: 'go/profile',
name: 'Go to profile',
keywords: 'navigate preferences',
perform: () => locationService.push('/profile'),
section: 'Navigation',
shortcut: ['g', 'p'],
priority: Priority.LOW,
},
],
},
{
url: '/datasources',
actions: [
{
id: 'go/configuration',
name: 'Go to data sources configuration',
keywords: 'navigate settings ds',
perform: () => locationService.push('/datasources'),
section: 'Navigation',
},
],
},
];
const navBarActions: Action[] = [];
const navBarFlatUrls = flatMapDeep(navBarTree, (nbt) => nbt.children)
.map((nbf) => nbf?.url)
.filter((url) => url !== undefined);
navBarActionMap.forEach((navBarAction) => {
const navBarItem = navBarFlatUrls.find((url) => navBarAction.url === url);
if (navBarItem) {
navBarActions.push(...navBarAction.actions);
}
});
return [...globalActions, ...navBarActions];
};

View File

@ -0,0 +1,117 @@
import { Action, Priority } from 'kbar';
import React from 'react';
import { isIconName, NavModelItem } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { Icon } from '@grafana/ui';
const SECTION_PAGES = 'Pages';
const SECTION_ACTIONS = 'Actions';
const SECTION_PREFERENCES = 'Preferences';
export interface NavBarActions {
url: string;
actions: Action[];
}
// TODO: Clean this once ID is mandatory on nav items
function idForNavItem(navItem: NavModelItem) {
return 'navModel.' + navItem.id ?? navItem.url ?? navItem.text ?? navItem.subTitle;
}
function navTreeToActions(navTree: NavModelItem[], parent?: NavModelItem): Action[] {
const navActions: Action[] = [];
for (const navItem of navTree) {
const { url, text, isCreateAction, children } = navItem;
const hasChildren = Boolean(children?.length);
if (!(url || hasChildren)) {
continue;
}
const action: Action = {
id: idForNavItem(navItem),
name: text, // TODO: translate
section: isCreateAction ? SECTION_ACTIONS : SECTION_PAGES,
perform: url ? () => locationService.push(url) : undefined,
parent: parent && idForNavItem(parent),
// Only show icons for top level items
icon: !parent && iconForNavItem(navItem),
};
navActions.push(action);
if (children?.length) {
const childActions = navTreeToActions(children, navItem);
navActions.push(...childActions);
}
}
return navActions;
}
export default (navBarTree: NavModelItem[]) => {
const globalActions: Action[] = [
{
// TODO: Figure out what section, if any, to put this in
id: 'go/dashboard',
name: 'Dashboards...',
keywords: 'navigate',
priority: Priority.NORMAL,
},
{
id: 'go/search',
name: 'Search',
keywords: 'navigate',
icon: <Icon name="search" size="md" />,
perform: () => locationService.push('?search=open'),
section: SECTION_PAGES,
shortcut: ['s', 'o'],
},
{
id: 'preferences/theme',
name: 'Change theme...',
keywords: 'interface color dark light',
section: SECTION_PREFERENCES,
shortcut: ['c', 't'],
},
{
id: 'preferences/dark-theme',
name: 'Dark',
keywords: 'dark theme',
section: '',
perform: () => {
locationService.push({ search: '?theme=dark' });
location.reload();
},
parent: 'preferences/theme',
},
{
id: 'preferences/light-theme',
name: 'Light',
keywords: 'light theme',
section: '',
perform: () => {
locationService.push({ search: '?theme=light' });
location.reload();
},
parent: 'preferences/theme',
},
];
const navBarActions = navTreeToActions(navBarTree);
return [...globalActions, ...navBarActions];
};
function iconForNavItem(navItem: NavModelItem) {
if (navItem.icon && isIconName(navItem.icon)) {
return <Icon name={navItem.icon} size="md" />;
} else if (navItem.img) {
return <img alt="" src={navItem.img} style={{ width: 16, height: 16 }} />;
}
return undefined;
}