From 3e7db088acd1da32ea489b0756cdf4e15ec2da69 Mon Sep 17 00:00:00 2001 From: Kristina Date: Thu, 21 Apr 2022 16:50:34 -0500 Subject: [PATCH] Command Palette Scaffolding + Explore (#47445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add feature flag and scaffodling * start adding actions * WIP * move action files * Start adding styles * Fix implementation based on feedback * Add more hackathon code back to command palette * Cleanup * Cleanup unused service files for simple MVP pass * Move type def to library * WIP * Move provider to proper place to pick up other routes’ actions * Build actions off navbar, add explore actions * Work around undefined typescript stuff * Fix based on feedback * close palette on ESC * Fix based on PR feedback pt 1 * Move styles to classes * Move another inline style to a class * Enable command palette by default * change around async hook structure * Add simple feature tracking * Code cleanup, and be sure the command is accurate * Change to only render if there are actions, and only add actions once past login --- conf/defaults.ini | 3 + package.json | 1 + .../src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 5 + pkg/services/featuremgmt/toggles_gen.go | 4 + public/app/AppWrapper.tsx | 62 ++++--- .../commandPalette/CommandPalette.tsx | 146 +++++++++++++++++ .../features/commandPalette/ResultItem.tsx | 123 ++++++++++++++ .../actions/dashboard.nav.actions.ts | 24 +++ .../actions/global.static.actions.ts | 152 ++++++++++++++++++ .../app/features/explore/ExploreActions.tsx | 94 +++++++++++ public/app/features/explore/Wrapper.tsx | 2 + yarn.lock | 83 ++++++++++ 13 files changed, 676 insertions(+), 24 deletions(-) create mode 100644 public/app/features/commandPalette/CommandPalette.tsx create mode 100644 public/app/features/commandPalette/ResultItem.tsx create mode 100644 public/app/features/commandPalette/actions/dashboard.nav.actions.ts create mode 100644 public/app/features/commandPalette/actions/global.static.actions.ts create mode 100644 public/app/features/explore/ExploreActions.tsx diff --git a/conf/defaults.ini b/conf/defaults.ini index 904719d5620..4bb5376c233 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1140,6 +1140,9 @@ promQueryBuilder = true # Experimental Explore to Dashboard workflow explore2Dashboard = true +# Experimental Command Palette +commandPalette = true + # feature1 = true # feature2 = false diff --git a/package.json b/package.json index d23dfb7b501..3dcbc26e117 100644 --- a/package.json +++ b/package.json @@ -319,6 +319,7 @@ "js-yaml": "^4.1.0", "json-source-map": "0.6.1", "jsurl": "^0.1.5", + "kbar": "^0.1.0-beta.30", "lezer-promql": "0.22.0", "lodash": "4.17.21", "logfmt": "^1.3.2", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index c222161310a..e5b4cb3e532 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -55,4 +55,5 @@ export interface FeatureToggles { azureMonitorResourcePickerForMetrics?: boolean; explore2Dashboard?: boolean; persistNotifications?: boolean; + commandPalette?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 668426522bf..6c18fb2a947 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -219,5 +219,10 @@ var ( State: FeatureStateAlpha, FrontendOnly: true, }, + { + Name: "commandPalette", + Description: "Enable command palette", + State: FeatureStateAlpha, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 9d92cc4cf21..0d762f26555 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -162,4 +162,8 @@ const ( // FlagPersistNotifications // PoC Notifications page FlagPersistNotifications = "persistNotifications" + + // FlagCommandPalette + // Enable command palette + FlagCommandPalette = "commandPalette" ) diff --git a/public/app/AppWrapper.tsx b/public/app/AppWrapper.tsx index 73a370a25dc..f31867db17f 100644 --- a/public/app/AppWrapper.tsx +++ b/public/app/AppWrapper.tsx @@ -1,6 +1,6 @@ import React, { ComponentType } from 'react'; import { Router, Route, Redirect, Switch } from 'react-router-dom'; -import { config, locationService, navigationLogger } from '@grafana/runtime'; +import { config, locationService, navigationLogger, reportInteraction } from '@grafana/runtime'; import { Provider } from 'react-redux'; import { store } from 'app/store/store'; import { ErrorBoundaryAlert, GlobalStyles, ModalRoot, ModalsProvider, PortalContainer } from '@grafana/ui'; @@ -15,6 +15,8 @@ import { GrafanaRoute } from './core/navigation/GrafanaRoute'; import { AppNotificationList } from './core/components/AppNotifications/AppNotificationList'; import { SearchWrapper } from 'app/features/search'; import { LiveConnectionWarning } from './features/live/LiveConnectionWarning'; +import { Action, KBarProvider } from 'kbar'; +import { CommandPalette } from './features/commandPalette/CommandPalette'; import { I18nProvider } from './core/localisation'; import { AngularRoot } from './angular/AngularRoot'; import { loadAndInitAngularIfEnabled } from './angular/loadAndInitAngularIfEnabled'; @@ -85,36 +87,48 @@ export class AppWrapper extends React.Component { + reportInteraction('commandPalette_action_selected', { + actionId: action.id, + }); + }; + return ( - - -
- - {ready && <>{newNavigationEnabled ? : }} -
- {pageBanners.map((Banner, index) => ( - - ))} + + + + {config.featureToggles.commandPalette && } +
+ + {ready && <>{newNavigationEnabled ? : }} +
+ {pageBanners.map((Banner, index) => ( + + ))} - - - - {ready && this.renderRoutes()} - {bodyRenderHooks.map((Hook, index) => ( - - ))} -
-
-
- - - -
+ + + + {ready && this.renderRoutes()} + {bodyRenderHooks.map((Hook, index) => ( + + ))} +
+
+
+ + + +
+
diff --git a/public/app/features/commandPalette/CommandPalette.tsx b/public/app/features/commandPalette/CommandPalette.tsx new file mode 100644 index 00000000000..4cdcc4c069d --- /dev/null +++ b/public/app/features/commandPalette/CommandPalette.tsx @@ -0,0 +1,146 @@ +import React, { useEffect, useState } from 'react'; +import { + KBarAnimator, + KBarPortal, + KBarPositioner, + KBarResults, + KBarSearch, + useMatches, + Action, + VisualState, + useRegisterActions, + useKBar, +} from 'kbar'; +import { useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { ResultItem } from './ResultItem'; +import getGlobalActions from './actions/global.static.actions'; +import getDashboardNavActions from './actions/dashboard.nav.actions'; +import { useSelector } from 'react-redux'; +import { StoreState } from 'app/types'; +import { css } from '@emotion/css'; +import { keybindingSrv } from '../../core/services/keybindingSrv'; +import { reportInteraction, locationService } from '@grafana/runtime'; + +/** + * Wrap all the components from KBar here. + * @constructor + */ + +export const CommandPalette = () => { + const styles = useStyles2(getSearchStyles); + const [actions, setActions] = useState([]); + const { notHidden, query, showing } = useKBar((state) => ({ + notHidden: state.visualState !== VisualState.hidden, + showing: state.visualState === VisualState.showing, + })); + const isNotLogin = locationService.getLocation().pathname !== '/login'; + + const { navBarTree } = useSelector((state: StoreState) => { + return { + navBarTree: state.navBarTree, + }; + }); + + keybindingSrv.bind('esc', () => { + if (notHidden) { + query.setVisualState(VisualState.animatingOut); + } + }); + + useEffect(() => { + (async () => { + if (isNotLogin) { + const staticActions = getGlobalActions(navBarTree); + const dashAct = await getDashboardNavActions('go/dashboard'); + setActions([...staticActions, ...dashAct]); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isNotLogin]); + + useEffect(() => { + if (showing) { + reportInteraction('commandPalette_opened'); + } + }, [showing]); + + useRegisterActions(actions, [actions]); + + return actions.length > 0 ? ( + + + + + + + + + ) : null; +}; + +const RenderResults = () => { + const { results, rootActionId } = useMatches(); + const styles = useStyles2(getSearchStyles); + + return ( +
+ + typeof item === 'string' ? ( +
{item}
+ ) : ( + + ) + } + /> +
+ ); +}; + +const getSearchStyles = (theme: GrafanaTheme2) => ({ + positioner: css({ + zIndex: theme.zIndex.portal, + marginTop: '0px', + '&::before': { + content: '""', + position: 'fixed', + top: 0, + right: 0, + bottom: 0, + left: 0, + background: theme.components.overlay.background, + backdropFilter: 'blur(1px)', + }, + }), + animator: css({ + maxWidth: theme.breakpoints.values.sm, // supposed to be 600... + width: '100%', + background: theme.colors.background.canvas, + color: theme.colors.text.primary, + borderRadius: theme.shape.borderRadius(4), + overflow: 'hidden', + boxShadow: theme.shadows.z3, + }), + search: css({ + padding: theme.spacing(2, 3), + fontSize: theme.typography.fontSize, + width: '100%', + boxSizing: 'border-box', + outline: 'none', + border: 'none', + background: theme.colors.background.canvas, + color: theme.colors.text.primary, + borderBottom: `1px solid ${theme.colors.border.weak}`, + }), + sectionHeader: css({ + padding: theme.spacing(1, 2), + fontSize: theme.typography.h6.fontSize, + fontWeight: theme.typography.body.fontWeight, + color: theme.colors.text.secondary, + }), + resultsContainer: css({ + padding: theme.spacing(2, 0), + }), +}); diff --git a/public/app/features/commandPalette/ResultItem.tsx b/public/app/features/commandPalette/ResultItem.tsx new file mode 100644 index 00000000000..2b876a74f48 --- /dev/null +++ b/public/app/features/commandPalette/ResultItem.tsx @@ -0,0 +1,123 @@ +import React from 'react'; +import { ActionId, ActionImpl } from 'kbar'; +import { useTheme2 } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { css } from '@emotion/css'; + +export const ResultItem = React.forwardRef( + ( + { + action, + active, + currentRootActionId, + }: { + action: ActionImpl; + active: boolean; + currentRootActionId: ActionId; + }, + ref: React.Ref + ) => { + const ancestors = React.useMemo(() => { + if (!currentRootActionId) { + return action.ancestors; + } + + const index = action.ancestors.findIndex((ancestor) => ancestor.id === currentRootActionId); + // +1 removes the currentRootAction; e.g. + // if we are on the "Set theme" parent action, + // the UI should not display "Set theme… > Dark" + // but rather just "Dark" + return action.ancestors.slice(index + 1); + }, [action.ancestors, currentRootActionId]); + + const theme = useTheme2(); + const styles = getResultItemStyles(theme, active); + + return ( +
+
+ {action.icon} +
+
+ {ancestors.length > 0 && + ancestors.map((ancestor) => ( + + {ancestor.name} + + + ))} + {action.name} +
+
+ {action.subtitle && {action.subtitle}} +
+ {action.shortcut?.length ? ( +
+ {action.shortcut.map((sc) => ( + + {sc} + + ))} +
+ ) : null} +
+ ); + } +); +ResultItem.displayName = 'ResultItem'; + +const getResultItemStyles = (theme: GrafanaTheme2, isActive: boolean) => { + const textColor = isActive ? theme.colors.text.maxContrast : theme.colors.text.primary; + const rowBackgroundColor = isActive ? theme.colors.background.primary : 'transparent'; + const shortcutBackgroundColor = isActive ? theme.colors.background.secondary : theme.colors.background.primary; + return { + row: css({ + color: textColor, + padding: theme.spacing(1, 2), + background: rowBackgroundColor, + display: 'flex', + alightItems: 'center', + justifyContent: 'space-between', + cursor: 'pointer', + '&:before': { + display: isActive ? 'block' : 'none', + content: '" "', + position: 'absolute', + left: 0, + top: 0, + bottom: 0, + width: theme.spacing(0.5), + borderRadius: theme.shape.borderRadius(1), + backgroundImage: theme.colors.gradients.brandVertical, + }, + }), + actionContainer: css({ + display: 'flex', + gap: theme.spacing(2), + alignitems: 'center', + fontsize: theme.typography.fontSize, + }), + textContainer: css({ + display: 'flex', + flexDirection: 'column', + }), + shortcut: css({ + padding: theme.spacing(0, 1), + background: shortcutBackgroundColor, + borderRadius: theme.shape.borderRadius(), + fontsize: theme.typography.fontSize, + }), + breadcrumbAncestor: css({ + opacity: 0.5, + marginRight: theme.spacing(1), + }), + subtitleText: css({ + fontSize: theme.typography.fontSize - 2, + }), + shortcutContainer: css({ + display: 'grid', + gridAutoFlow: 'column', + gap: theme.spacing(1), + }), + }; +}; diff --git a/public/app/features/commandPalette/actions/dashboard.nav.actions.ts b/public/app/features/commandPalette/actions/dashboard.nav.actions.ts new file mode 100644 index 00000000000..38ca11e3eb8 --- /dev/null +++ b/public/app/features/commandPalette/actions/dashboard.nav.actions.ts @@ -0,0 +1,24 @@ +import { locationService, getBackendSrv } from '@grafana/runtime'; +import { Action } from 'kbar'; + +async function getDashboardNav(parentId: string): Promise { + const data: Array<{ type: string; title: string; url: string }> = await getBackendSrv().get('/api/search'); + + const goToDashboardActions: Action[] = data + .filter((item) => item.type === 'dash-db') + .map((item) => ({ + parent: parentId, + id: `go/dashboard/${item.url}`, + name: `Go to dashboard ${item.title}`, + perform: () => { + locationService.push(item.url); + }, + })); + + return goToDashboardActions; +} + +export default async (parentId: string) => { + const dashboardNav = await getDashboardNav(parentId); + return dashboardNav; +}; diff --git a/public/app/features/commandPalette/actions/global.static.actions.ts b/public/app/features/commandPalette/actions/global.static.actions.ts new file mode 100644 index 00000000000..c94f3c11c4a --- /dev/null +++ b/public/app/features/commandPalette/actions/global.static.actions.ts @@ -0,0 +1,152 @@ +import { locationService } from '@grafana/runtime'; +import { NavModelItem } from '@grafana/data'; +import { Action, Priority } from 'kbar'; + +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', + }, + { + 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 = [ + { + 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, + }, + ], + }, + { + url: '/alerting', + actions: [ + { + id: 'go/alerting', + name: 'Go to alerting', + keywords: 'navigate notification', + perform: () => locationService.push('/alerting'), + section: 'Navigation', + priority: Priority.NORMAL, + }, + ], + }, + { + 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[] = []; + + navBarActionMap.forEach((navBarAction) => { + const navBarItem = navBarTree.find((navBarItem) => navBarItem.url === navBarAction.url); + if (navBarItem && !navBarItem.hideFromNavbar) { + navBarActions.push(...navBarAction.actions); + } + }); + + return [...globalActions, ...navBarActions]; +}; diff --git a/public/app/features/explore/ExploreActions.tsx b/public/app/features/explore/ExploreActions.tsx new file mode 100644 index 00000000000..ac1b95e101e --- /dev/null +++ b/public/app/features/explore/ExploreActions.tsx @@ -0,0 +1,94 @@ +import { FC, useEffect, useState } from 'react'; +import { useRegisterActions, useKBar, Action, Priority } from 'kbar'; +import { useDispatch, useSelector } from 'react-redux'; +import { ExploreId } from 'app/types'; +import { splitOpen, splitClose } from './state/main'; +import { isSplit } from './state/selectors'; +import { runQueries } from './state/query'; + +interface Props { + exploreIdLeft: ExploreId; + exploreIdRight?: ExploreId; +} + +export const ExploreActions: FC = ({ exploreIdLeft, exploreIdRight }: Props) => { + const [actions, setActions] = useState([]); + const { query } = useKBar(); + const dispatch = useDispatch(); + const splitted = useSelector(isSplit); + + useEffect(() => { + const exploreSection = { + name: 'Explore', + priority: Priority.HIGH + 1, + }; + + const actionsArr: Action[] = []; + + if (splitted) { + actionsArr.push({ + id: 'explore/run-query-left', + name: 'Run Query (Left)', + keywords: 'query left', + perform: () => { + dispatch(runQueries(exploreIdLeft)); + }, + section: exploreSection, + }); + if (exploreIdRight) { + // we should always have the right exploreId if split + actionsArr.push({ + id: 'explore/run-query-right', + name: 'Run Query (Right)', + keywords: 'query right', + perform: () => { + dispatch(runQueries(exploreIdRight)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-close-left', + name: 'Close split view left', + keywords: 'split', + perform: () => { + dispatch(splitClose(exploreIdLeft)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-close-right', + name: 'Close split view right', + keywords: 'split', + perform: () => { + dispatch(splitClose(exploreIdRight)); + }, + section: exploreSection, + }); + } + } else { + actionsArr.push({ + id: 'explore/run-query', + name: 'Run Query', + keywords: 'query', + perform: () => { + dispatch(runQueries(exploreIdLeft)); + }, + section: exploreSection, + }); + actionsArr.push({ + id: 'explore/split-view-open', + name: 'Open split view', + keywords: 'split', + perform: () => { + dispatch(splitOpen()); + }, + section: exploreSection, + }); + } + setActions(actionsArr); + }, [exploreIdLeft, exploreIdRight, splitted, query, dispatch]); + + useRegisterActions(!query ? [] : actions, [actions, query]); + + return null; +}; diff --git a/public/app/features/explore/Wrapper.tsx b/public/app/features/explore/Wrapper.tsx index 4f2d5439ea4..e1591752591 100644 --- a/public/app/features/explore/Wrapper.tsx +++ b/public/app/features/explore/Wrapper.tsx @@ -6,6 +6,7 @@ import { lastSavedUrl, resetExploreAction, richHistoryUpdatedAction } from './st import { ExplorePaneContainer } from './ExplorePaneContainer'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { Branding } from '../../core/components/Branding/Branding'; +import { ExploreActions } from './ExploreActions'; import { getNavModel } from '../../core/selectors/navModel'; import { StoreState } from 'app/types'; @@ -69,6 +70,7 @@ class WrapperUnconnected extends PureComponent { return (
+
diff --git a/yarn.lock b/yarn.lock index ef51e3b4ee0..294116d626f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6881,6 +6881,40 @@ __metadata: languageName: node linkType: hard +"@reach/observe-rect@npm:^1.1.0": + version: 1.2.0 + resolution: "@reach/observe-rect@npm:1.2.0" + checksum: 7dd903eeaad0e22c6d973bd26265d91eadba56ab5134701ceb3e85214db75339fae94aa7e8b88a65e8daa64bc7cf1b915d4ffcdfd324466b561dc6adc3c6e070 + languageName: node + linkType: hard + +"@reach/portal@npm:^0.16.0": + version: 0.16.2 + resolution: "@reach/portal@npm:0.16.2" + dependencies: + "@reach/utils": 0.16.0 + tiny-warning: ^1.0.3 + tslib: ^2.3.0 + peerDependencies: + react: ^16.8.0 || 17.x + react-dom: ^16.8.0 || 17.x + checksum: 7413dcd169cfb9854dd0d3a01f811ec19ef170558fcbd00118d676fc02c197d1c0bce1d1357508879d4775169561d103e13a8e4d74cc677eb0037cc7b04f7a1e + languageName: node + linkType: hard + +"@reach/utils@npm:0.16.0": + version: 0.16.0 + resolution: "@reach/utils@npm:0.16.0" + dependencies: + tiny-warning: ^1.0.3 + tslib: ^2.3.0 + peerDependencies: + react: ^16.8.0 || 17.x + react-dom: ^16.8.0 || 17.x + checksum: 36bc0eb41a71798eb6186b23de265ba709e51dae5bf214fb8505c66bb3f2e6a41bb2401457350436ba89ca9e3a50f93a04fe7c33d15648ce11e568a85622d770 + languageName: node + linkType: hard + "@react-aria/button@npm:3.4.3": version: 3.4.3 resolution: "@react-aria/button@npm:3.4.3" @@ -15087,6 +15121,13 @@ __metadata: languageName: node linkType: hard +"command-score@npm:^0.1.2": + version: 0.1.2 + resolution: "command-score@npm:0.1.2" + checksum: b733fd552d7e569070da3d474b1ed5f54785fdf3dd61670002e0a00b2eff1a547c2b6d3af3683c012f4f39c6455f9e7ee5e9997a79c08048ec37ec2195d3df08 + languageName: node + linkType: hard + "commander@npm:*, commander@npm:8.3.0, commander@npm:^8.0.0, commander@npm:^8.1.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -18998,6 +19039,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^2.0.3": + version: 2.0.4 + resolution: "fast-equals@npm:2.0.4" + checksum: 1aac8a2e16b33e5e402bb5cd46be65f1ca331903c2c44e3bd75324e8472ee04f0acdbc6889e45fc28f9707ca3964f2a86789b32305d4d8b85dc97f61e446ef4b + languageName: node + linkType: hard + "fast-glob@npm:^2.2.6": version: 2.2.7 resolution: "fast-glob@npm:2.2.7" @@ -20585,6 +20633,7 @@ __metadata: js-yaml: ^4.1.0 json-source-map: 0.6.1 jsurl: ^0.1.5 + kbar: ^0.1.0-beta.30 lerna: ^4.0.0 lezer-promql: 0.22.0 lint-staged: 12.3.7 @@ -24493,6 +24542,22 @@ __metadata: languageName: node linkType: hard +"kbar@npm:^0.1.0-beta.30": + version: 0.1.0-beta.33 + resolution: "kbar@npm:0.1.0-beta.33" + dependencies: + "@reach/portal": ^0.16.0 + command-score: ^0.1.2 + fast-equals: ^2.0.3 + react-virtual: ^2.8.2 + tiny-invariant: ^1.2.0 + peerDependencies: + react: ^16.0.0 || ^17.0.0 + react-dom: ^16.0.0 || ^17.0.0 + checksum: 545720900a046501157f8aab9530f62dec356438b85f728968fdc08192cc65afd903cd27e3a0c0f92a74ad6d15d808f5eb12c8b6a801cf881208e3dc2594eb8b + languageName: node + linkType: hard + "keycharm@npm:^0.2.0": version: 0.2.0 resolution: "keycharm@npm:0.2.0" @@ -31317,6 +31382,17 @@ __metadata: languageName: node linkType: hard +"react-virtual@npm:^2.8.2": + version: 2.10.4 + resolution: "react-virtual@npm:2.10.4" + dependencies: + "@reach/observe-rect": ^1.1.0 + peerDependencies: + react: ^16.6.3 || ^17.0.0 + checksum: 1bebc741b01057829a7d7f29256114caecf0597d41b187cb41e75af77f24a87c780bc1a81ec11205b78ee2e9c801fc5e36b20a9e1ab7ddc70a18dd95417795f8 + languageName: node + linkType: hard + "react-virtualized-auto-sizer@npm:1.0.6": version: 1.0.6 resolution: "react-virtualized-auto-sizer@npm:1.0.6" @@ -34919,6 +34995,13 @@ __metadata: languageName: node linkType: hard +"tiny-invariant@npm:^1.2.0": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade + languageName: node + linkType: hard + "tiny-warning@npm:^0.0.3": version: 0.0.3 resolution: "tiny-warning@npm:0.0.3"