mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
b12b5ed92f
commit
93eb8cb51d
@ -34,6 +34,13 @@ export const ResultItem = React.forwardRef(
|
|||||||
const theme = useTheme2();
|
const theme = useTheme2();
|
||||||
const styles = getResultItemStyles(theme, active);
|
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 (
|
return (
|
||||||
<div ref={ref} className={styles.row}>
|
<div ref={ref} className={styles.row}>
|
||||||
<div className={styles.actionContainer}>
|
<div className={styles.actionContainer}>
|
||||||
@ -47,7 +54,7 @@ export const ResultItem = React.forwardRef(
|
|||||||
<span className={styles.breadcrumbAncestor}>›</span>
|
<span className={styles.breadcrumbAncestor}>›</span>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
<span>{action.name}</span>
|
<span>{name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{action.subtitle && <span className={styles.subtitleText}>{action.subtitle}</span>}
|
{action.subtitle && <span className={styles.subtitleText}>{action.subtitle}</span>}
|
||||||
@ -94,9 +101,9 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => {
|
|||||||
}),
|
}),
|
||||||
actionContainer: css({
|
actionContainer: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: theme.spacing(2),
|
gap: theme.spacing(1),
|
||||||
alignitems: 'center',
|
alignItems: 'center',
|
||||||
fontsize: theme.typography.fontSize,
|
fontSize: theme.typography.fontSize,
|
||||||
}),
|
}),
|
||||||
textContainer: css({
|
textContainer: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -106,7 +113,7 @@ const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => {
|
|||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
background: shortcutBackgroundColor,
|
background: shortcutBackgroundColor,
|
||||||
borderRadius: theme.shape.borderRadius(),
|
borderRadius: theme.shape.borderRadius(),
|
||||||
fontsize: theme.typography.fontSize,
|
fontSize: theme.typography.fontSize,
|
||||||
}),
|
}),
|
||||||
breadcrumbAncestor: css({
|
breadcrumbAncestor: css({
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
|
@ -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 };
|
|
@ -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];
|
|
||||||
};
|
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user