From 93eb8cb51dacbbfde84777e5f8556074a12d6a5f Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 30 Nov 2022 17:34:25 +0000 Subject: [PATCH] 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 --- .../features/commandPalette/ResultItem.tsx | 17 +- .../actions/alerting.static.actions.ts | 73 --------- .../actions/global.static.actions.ts | 153 ------------------ .../actions/global.static.actions.tsx | 117 ++++++++++++++ 4 files changed, 129 insertions(+), 231 deletions(-) delete mode 100644 public/app/features/commandPalette/actions/alerting.static.actions.ts delete mode 100644 public/app/features/commandPalette/actions/global.static.actions.ts create mode 100644 public/app/features/commandPalette/actions/global.static.actions.tsx diff --git a/public/app/features/commandPalette/ResultItem.tsx b/public/app/features/commandPalette/ResultItem.tsx index 6f817fac456..036d70dffb4 100644 --- a/public/app/features/commandPalette/ResultItem.tsx +++ b/public/app/features/commandPalette/ResultItem.tsx @@ -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 (
@@ -47,7 +54,7 @@ export const ResultItem = React.forwardRef( › ))} - {action.name} + {name}
{action.subtitle && {action.subtitle}} @@ -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, diff --git a/public/app/features/commandPalette/actions/alerting.static.actions.ts b/public/app/features/commandPalette/actions/alerting.static.actions.ts deleted file mode 100644 index d4ceea7e138..00000000000 --- a/public/app/features/commandPalette/actions/alerting.static.actions.ts +++ /dev/null @@ -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 }; diff --git a/public/app/features/commandPalette/actions/global.static.actions.ts b/public/app/features/commandPalette/actions/global.static.actions.ts deleted file mode 100644 index 7fb13a30a0f..00000000000 --- a/public/app/features/commandPalette/actions/global.static.actions.ts +++ /dev/null @@ -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]; -}; diff --git a/public/app/features/commandPalette/actions/global.static.actions.tsx b/public/app/features/commandPalette/actions/global.static.actions.tsx new file mode 100644 index 00000000000..505b89fb798 --- /dev/null +++ b/public/app/features/commandPalette/actions/global.static.actions.tsx @@ -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: , + 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 ; + } else if (navItem.img) { + return ; + } + + return undefined; +}