Add open Assistant keyboard shortcut (#112228)
This commit is contained in:
+1
-1
@@ -279,7 +279,7 @@
|
||||
"@formatjs/intl-durationformat": "^0.7.0",
|
||||
"@glideapps/glide-data-grid": "^6.0.0",
|
||||
"@grafana/alerting": "workspace:*",
|
||||
"@grafana/assistant": "0.0.18",
|
||||
"@grafana/assistant": "0.1.0",
|
||||
"@grafana/aws-sdk": "0.7.1",
|
||||
"@grafana/azure-sdk": "0.0.7",
|
||||
"@grafana/data": "workspace:*",
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grafana/assistant": "^0.0.17 || ^0.0.18",
|
||||
"@grafana/assistant": "^0.1.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import FlameGraphContainer, { labelSearch } from './FlameGraphContainer';
|
||||
import { MIN_WIDTH_TO_SHOW_BOTH_TOPTABLE_AND_FLAMEGRAPH } from './constants';
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
useAssistant: jest.fn(() => [false, null]), // [isAvailable, openAssistant]
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: undefined,
|
||||
}),
|
||||
createAssistantContextItem: jest.fn(),
|
||||
OpenAssistantButton: () => <div>OpenAssistantButton</div>,
|
||||
}));
|
||||
|
||||
@@ -8,7 +8,10 @@ import FlameGraphHeader from './FlameGraphHeader';
|
||||
import { ColorScheme, SelectedView } from './types';
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
useAssistant: jest.fn(() => [false, null]), // [isAvailable, openAssistant]
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: undefined,
|
||||
}),
|
||||
createAssistantContextItem: jest.fn(),
|
||||
OpenAssistantButton: () => <div>OpenAssistantButton</div>,
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { useAssistant } from '@grafana/assistant';
|
||||
|
||||
import { useShortcuts } from './HelpModal';
|
||||
|
||||
// Mock the assistant hook
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
useAssistant: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock getModKey
|
||||
jest.mock('app/core/utils/browser', () => ({
|
||||
getModKey: jest.fn(() => 'ctrl'),
|
||||
}));
|
||||
|
||||
const mockUseAssistant = useAssistant as jest.MockedFunction<typeof useAssistant>;
|
||||
|
||||
describe('useShortcuts', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return shortcuts without assistant shortcut when assistant is not available', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: false,
|
||||
toggleAssistant: jest.fn(),
|
||||
} as unknown as ReturnType<typeof useAssistant>);
|
||||
|
||||
const { result } = renderHook(() => useShortcuts());
|
||||
|
||||
expect(result.current).toHaveLength(4); // Global, Time range, Dashboard, Focused panel
|
||||
|
||||
// Check that global shortcuts don't include assistant shortcut
|
||||
const globalCategory = result.current.find((category) => category.category.includes('Global'));
|
||||
expect(globalCategory).toBeDefined();
|
||||
|
||||
const assistantShortcut = globalCategory!.shortcuts.find((shortcut) => shortcut.keys.includes('ctrl + .'));
|
||||
expect(assistantShortcut).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return shortcuts with assistant shortcut when assistant is available', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useShortcuts());
|
||||
|
||||
expect(result.current).toHaveLength(4); // Global, Time range, Dashboard, Focused panel
|
||||
|
||||
// Check that global shortcuts include assistant shortcut
|
||||
const globalCategory = result.current.find((category) => category.category.includes('Global'));
|
||||
expect(globalCategory).toBeDefined();
|
||||
|
||||
const assistantShortcut = globalCategory!.shortcuts.find((shortcut) => shortcut.keys.includes('ctrl + .'));
|
||||
expect(assistantShortcut).toBeDefined();
|
||||
expect(assistantShortcut!.description).toContain('Assistant');
|
||||
});
|
||||
|
||||
it('should include all expected shortcut categories', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useShortcuts());
|
||||
|
||||
const categories = result.current.map((category) => category.category);
|
||||
|
||||
expect(categories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining('Global'),
|
||||
expect.stringContaining('Time range'),
|
||||
expect.stringContaining('Dashboard'),
|
||||
expect.stringContaining('Focused panel'),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should use the correct modKey in shortcuts', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useShortcuts());
|
||||
|
||||
// Find a shortcut that uses modKey (like save dashboard)
|
||||
const dashboardCategory = result.current.find((category) => category.category.includes('Dashboard'));
|
||||
const saveShortcut = dashboardCategory!.shortcuts.find((shortcut) =>
|
||||
shortcut.description.includes('Save dashboard')
|
||||
);
|
||||
|
||||
expect(saveShortcut).toBeDefined();
|
||||
expect(saveShortcut!.keys[0]).toBe('ctrl + s');
|
||||
});
|
||||
|
||||
it('should memoize results when dependencies do not change', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(() => useShortcuts());
|
||||
const firstResult = result.current;
|
||||
|
||||
// Rerender without changing dependencies
|
||||
rerender();
|
||||
|
||||
// Should return the same reference (memoized)
|
||||
expect(result.current).toBe(firstResult);
|
||||
});
|
||||
|
||||
it('should update when assistant availability changes', () => {
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: false,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(() => useShortcuts());
|
||||
const firstResult = result.current;
|
||||
|
||||
// Change assistant availability
|
||||
mockUseAssistant.mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
closeAssistant: jest.fn(),
|
||||
toggleAssistant: jest.fn(),
|
||||
});
|
||||
rerender();
|
||||
|
||||
// Should return a different reference (not memoized)
|
||||
expect(result.current).not.toBe(firstResult);
|
||||
|
||||
// And should now include assistant shortcut
|
||||
const globalCategory = result.current.find((category) => category.category.includes('Global'));
|
||||
const assistantShortcut = globalCategory!.shortcuts.find((shortcut) => shortcut.keys.includes('ctrl + .'));
|
||||
expect(assistantShortcut).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -1,173 +1,19 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useAssistant } from '@grafana/assistant';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Grid, Modal, useStyles2, Text } from '@grafana/ui';
|
||||
import { getModKey } from 'app/core/utils/browser';
|
||||
|
||||
const getShortcuts = (modKey: string) => {
|
||||
return [
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.global', 'Global'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['g', 'h'],
|
||||
description: t('help-modal.shortcuts-description.go-to-home-dashboard', 'Go to Home Dashboard'),
|
||||
},
|
||||
{
|
||||
keys: ['g', 'd'],
|
||||
description: t('help-modal.shortcuts-description.go-to-dashboards', 'Go to Dashboards'),
|
||||
},
|
||||
{ keys: ['g', 'e'], description: t('help-modal.shortcuts-description.go-to-explore', 'Go to Explore') },
|
||||
{ keys: ['g', 'p'], description: t('help-modal.shortcuts-description.go-to-profile', 'Go to Profile') },
|
||||
{ keys: [`${modKey} + k`], description: t('help-modal.shortcuts-description.open-search', 'Open search') },
|
||||
{
|
||||
keys: ['esc'],
|
||||
description: t('help-modal.shortcuts-description.exit-edit/setting-views', 'Exit edit/setting views'),
|
||||
},
|
||||
{
|
||||
keys: ['?'],
|
||||
description: t('help-modal.shortcuts-description.show-all-shortcuts', 'Show all keyboard shortcuts'),
|
||||
},
|
||||
{ keys: ['c', 't'], description: t('help-modal.shortcuts-description.change-theme', 'Change theme') },
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.time-range', 'Time range'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['t', 'z'],
|
||||
description: t('help-modal.shortcuts-description.zoom-out-time-range', 'Zoom out time range'),
|
||||
},
|
||||
{
|
||||
keys: ['t', '←'],
|
||||
description: t('help-modal.shortcuts-description.move-time-range-back', 'Move time range back'),
|
||||
},
|
||||
{
|
||||
keys: ['t', '→'],
|
||||
description: t('help-modal.shortcuts-description.move-time-range-forward', 'Move time range forward'),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'a'],
|
||||
description: t(
|
||||
'help-modal.shortcuts-description.make-time-range-permanent',
|
||||
'Make time range absolute/permanent'
|
||||
),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'c'],
|
||||
description: t('help-modal.shortcuts-description.copy-time-range', 'Copy time range'),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'v'],
|
||||
description: t('help-modal.shortcuts-description.paste-time-range', 'Paste time range'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.dashboard', 'Dashboard'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: [`${modKey} + s`],
|
||||
description: t('help-modal.shortcuts-description.save-dashboard', 'Save dashboard'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'r'],
|
||||
description: t('help-modal.shortcuts-description.refresh-all-panels', 'Refresh all panels'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 's'],
|
||||
description: t('help-modal.shortcuts-description.dashboard-settings', 'Dashboard settings'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'v'],
|
||||
description: t('help-modal.shortcuts-description.toggle-active-mode', 'Toggle in-active / view mode'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'k'],
|
||||
description: t('help-modal.shortcuts-description.toggle-kiosk', 'Toggle kiosk mode (hides top nav)'),
|
||||
},
|
||||
{
|
||||
keys: ['d', '⇧ + e'],
|
||||
description: t('help-modal.shortcuts-description.expand-all-rows', 'Expand all rows'),
|
||||
},
|
||||
{
|
||||
keys: ['d', '⇧ + c'],
|
||||
description: t('help-modal.shortcuts-description.collapse-all-rows', 'Collapse all rows'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'a'],
|
||||
description: t(
|
||||
'help-modal.shortcuts-description.toggle-auto-fit',
|
||||
'Toggle auto fit panels (experimental feature)'
|
||||
),
|
||||
},
|
||||
{
|
||||
keys: [`${modKey} + o`],
|
||||
description: t('help-modal.shortcuts-description.toggle-graph-crosshair', 'Toggle shared graph crosshair'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'l'],
|
||||
description: t('help-modal.shortcuts-description.toggle-all-panel-legends', 'Toggle all panel legends'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'x'],
|
||||
description: t('help-modal.shortcuts-description.toggle-exemplars', 'Toggle exemplars in all panel'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.focused-panel', 'Focused panel'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['e'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-edit', 'Toggle panel edit view'),
|
||||
},
|
||||
{
|
||||
keys: ['v'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-fullscreen', 'Toggle panel fullscreen view'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'u'],
|
||||
description: t('help-modal.shortcuts-description.open-share-link-drawer', 'Share panel link'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'e'],
|
||||
description: t('help-modal.shortcuts-description.open-share-embed-drawer', 'Share panel embed'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 's'],
|
||||
description: t('help-modal.shortcuts-description.open-shared-modal', 'Share panel snapshot'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'x'],
|
||||
description: t('help-modal.shortcuts-description.explore-panel', 'Explore panel'),
|
||||
},
|
||||
{
|
||||
keys: ['i'],
|
||||
description: t('help-modal.shortcuts-description.inspect-panel', 'Inspect panel'),
|
||||
},
|
||||
{ keys: ['p', 'd'], description: t('help-modal.shortcuts-description.duplicate-panel', 'Duplicate Panel') },
|
||||
{ keys: ['p', 'r'], description: t('help-modal.shortcuts-description.remove-panel', 'Remove Panel') },
|
||||
{
|
||||
keys: ['p', 'l'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-legend', 'Toggle panel legend'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export interface HelpModalProps {
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const modKey = useMemo(() => getModKey(), []);
|
||||
const shortcuts = useMemo(() => getShortcuts(modKey), [modKey]);
|
||||
const shortcuts = useShortcuts();
|
||||
return (
|
||||
<Modal title={t('help-modal.title', 'Shortcuts')} isOpen onDismiss={onDismiss} onClickBackdrop={onDismiss}>
|
||||
<Grid columns={{ xs: 1, sm: 2 }} gap={3} tabIndex={0}>
|
||||
@@ -213,6 +59,175 @@ export const HelpModal = ({ onDismiss }: HelpModalProps): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
export const useShortcuts = () => {
|
||||
const { isAvailable: assistantAvailable } = useAssistant();
|
||||
const modKey = useMemo(() => getModKey(), []);
|
||||
|
||||
return useMemo(() => {
|
||||
const globalShortcuts = [
|
||||
{
|
||||
keys: ['g', 'h'],
|
||||
description: t('help-modal.shortcuts-description.go-to-home-dashboard', 'Go to Home Dashboard'),
|
||||
},
|
||||
{
|
||||
keys: ['g', 'd'],
|
||||
description: t('help-modal.shortcuts-description.go-to-dashboards', 'Go to Dashboards'),
|
||||
},
|
||||
{ keys: ['g', 'e'], description: t('help-modal.shortcuts-description.go-to-explore', 'Go to Explore') },
|
||||
{ keys: ['g', 'p'], description: t('help-modal.shortcuts-description.go-to-profile', 'Go to Profile') },
|
||||
{ keys: ['g', 'a'], description: t('help-modal.shortcuts-description.open-alerting', 'Go to Alerting') },
|
||||
{ keys: [`${modKey} + k`], description: t('help-modal.shortcuts-description.open-search', 'Open search') },
|
||||
{
|
||||
keys: ['esc'],
|
||||
description: t('help-modal.shortcuts-description.exit-edit/setting-views', 'Exit edit/setting views'),
|
||||
},
|
||||
{
|
||||
keys: ['?'],
|
||||
description: t('help-modal.shortcuts-description.show-all-shortcuts', 'Show all keyboard shortcuts'),
|
||||
},
|
||||
{ keys: ['c', 't'], description: t('help-modal.shortcuts-description.change-theme', 'Change theme') },
|
||||
];
|
||||
|
||||
// Add assistant shortcut only if assistant is available
|
||||
if (assistantAvailable) {
|
||||
globalShortcuts.push({
|
||||
keys: [`${modKey} + .`],
|
||||
description: t('help-modal.shortcuts-description.open-assistant', 'Open Assistant'),
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.global', 'Global'),
|
||||
shortcuts: globalShortcuts,
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.time-range', 'Time range'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['t', 'z'],
|
||||
description: t('help-modal.shortcuts-description.zoom-out-time-range', 'Zoom out time range'),
|
||||
},
|
||||
{
|
||||
keys: ['t', '←'],
|
||||
description: t('help-modal.shortcuts-description.move-time-range-back', 'Move time range back'),
|
||||
},
|
||||
{
|
||||
keys: ['t', '→'],
|
||||
description: t('help-modal.shortcuts-description.move-time-range-forward', 'Move time range forward'),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'a'],
|
||||
description: t(
|
||||
'help-modal.shortcuts-description.make-time-range-permanent',
|
||||
'Make time range absolute/permanent'
|
||||
),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'c'],
|
||||
description: t('help-modal.shortcuts-description.copy-time-range', 'Copy time range'),
|
||||
},
|
||||
{
|
||||
keys: ['t', 'v'],
|
||||
description: t('help-modal.shortcuts-description.paste-time-range', 'Paste time range'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.dashboard', 'Dashboard'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: [`${modKey} + s`],
|
||||
description: t('help-modal.shortcuts-description.save-dashboard', 'Save dashboard'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'r'],
|
||||
description: t('help-modal.shortcuts-description.refresh-all-panels', 'Refresh all panels'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 's'],
|
||||
description: t('help-modal.shortcuts-description.dashboard-settings', 'Dashboard settings'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'v'],
|
||||
description: t('help-modal.shortcuts-description.toggle-active-mode', 'Toggle in-active / view mode'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'k'],
|
||||
description: t('help-modal.shortcuts-description.toggle-kiosk', 'Toggle kiosk mode (hides top nav)'),
|
||||
},
|
||||
{
|
||||
keys: ['d', '⇧ + e'],
|
||||
description: t('help-modal.shortcuts-description.expand-all-rows', 'Expand all rows'),
|
||||
},
|
||||
{
|
||||
keys: ['d', '⇧ + c'],
|
||||
description: t('help-modal.shortcuts-description.collapse-all-rows', 'Collapse all rows'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'a'],
|
||||
description: t(
|
||||
'help-modal.shortcuts-description.toggle-auto-fit',
|
||||
'Toggle auto fit panels (experimental feature)'
|
||||
),
|
||||
},
|
||||
{
|
||||
keys: [`${modKey} + o`],
|
||||
description: t('help-modal.shortcuts-description.toggle-graph-crosshair', 'Toggle shared graph crosshair'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'l'],
|
||||
description: t('help-modal.shortcuts-description.toggle-all-panel-legends', 'Toggle all panel legends'),
|
||||
},
|
||||
{
|
||||
keys: ['d', 'x'],
|
||||
description: t('help-modal.shortcuts-description.toggle-exemplars', 'Toggle exemplars in all panel'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: t('help-modal.shortcuts-category.focused-panel', 'Focused panel'),
|
||||
shortcuts: [
|
||||
{
|
||||
keys: ['e'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-edit', 'Toggle panel edit view'),
|
||||
},
|
||||
{
|
||||
keys: ['v'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-fullscreen', 'Toggle panel fullscreen view'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'u'],
|
||||
description: t('help-modal.shortcuts-description.open-share-link-drawer', 'Share panel link'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'e'],
|
||||
description: t('help-modal.shortcuts-description.open-share-embed-drawer', 'Share panel embed'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 's'],
|
||||
description: t('help-modal.shortcuts-description.open-shared-modal', 'Share panel snapshot'),
|
||||
},
|
||||
{
|
||||
keys: ['p', 'x'],
|
||||
description: t('help-modal.shortcuts-description.explore-panel', 'Explore panel'),
|
||||
},
|
||||
{
|
||||
keys: ['i'],
|
||||
description: t('help-modal.shortcuts-description.inspect-panel', 'Inspect panel'),
|
||||
},
|
||||
{ keys: ['p', 'd'], description: t('help-modal.shortcuts-description.duplicate-panel', 'Duplicate Panel') },
|
||||
{ keys: ['p', 'r'], description: t('help-modal.shortcuts-description.remove-panel', 'Remove Panel') },
|
||||
{
|
||||
keys: ['p', 'l'],
|
||||
description: t('help-modal.shortcuts-description.toggle-panel-legend', 'Toggle panel legend'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [modKey, assistantAvailable]);
|
||||
};
|
||||
|
||||
interface KeyProps {
|
||||
children: string;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { toggleAssistant, isAssistantAvailable } from '@grafana/assistant';
|
||||
import { LegacyGraphHoverClearEvent, SetPanelAttentionEvent, locationUtil } from '@grafana/data';
|
||||
import { LocationService } from '@grafana/runtime';
|
||||
import appEvents from 'app/core/app_events';
|
||||
@@ -38,6 +39,7 @@ export class KeybindingSrv {
|
||||
}
|
||||
/** string for VizPanel key and number for panelId */
|
||||
private panelId: string | number | null = null;
|
||||
private assistantSubscription: { unsubscribe: () => void } | null = null;
|
||||
|
||||
clearAndInitGlobalBindings(route: RouteDescriptor) {
|
||||
mousetrap.reset();
|
||||
@@ -51,6 +53,8 @@ export class KeybindingSrv {
|
||||
this.bind('g e', this.goToExplore);
|
||||
this.bind('g a', this.openAlerting);
|
||||
this.bind('g p', this.goToProfile);
|
||||
// Conditionally bind open Assistant shortcut ('o a') if Assistant is available
|
||||
this.bindAssistantShortcutIfAvailable();
|
||||
this.bind('esc', this.exit);
|
||||
this.bindGlobalEsc();
|
||||
}
|
||||
@@ -120,6 +124,30 @@ export class KeybindingSrv {
|
||||
appEvents.publish(new ShowModalReactEvent({ component: HelpModal }));
|
||||
}
|
||||
|
||||
private bindAssistantShortcutIfAvailable() {
|
||||
// Clean up any existing subscription
|
||||
if (this.assistantSubscription) {
|
||||
this.assistantSubscription.unsubscribe();
|
||||
}
|
||||
// Subscribe to assistant availability and bind/unbind shortcut accordingly
|
||||
this.assistantSubscription = isAssistantAvailable().subscribe((available) => {
|
||||
if (available) {
|
||||
this.bind('mod+.', this.toggleAssistant);
|
||||
} else {
|
||||
// Unbind the shortcut if assistant becomes unavailable
|
||||
mousetrap.unbind('mod+.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private toggleAssistant() {
|
||||
toggleAssistant({
|
||||
origin: 'grafana/keyboard-shortcut',
|
||||
prompt: '',
|
||||
context: [],
|
||||
});
|
||||
}
|
||||
|
||||
private exit() {
|
||||
const search = this.locationService.getSearchObject();
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ import { LogLineVirtualization } from './virtualization';
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn(() => [true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('./LogListContext');
|
||||
|
||||
@@ -14,7 +14,10 @@ import { DEFAULT_TIME_WINDOW, LogLineContext, PAGE_SIZE } from './LogLineContext
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn(() => [true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const dfBefore = createDataFrame({
|
||||
|
||||
@@ -30,7 +30,10 @@ import { defaultValue } from './__mocks__/LogListContext';
|
||||
jest.mock('@grafana/assistant', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn().mockReturnValue([true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ jest.mock('./LogListContext');
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn(() => [true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
openAssistant: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
|
||||
@@ -21,7 +21,9 @@ import { LogList, Props } from './LogList';
|
||||
|
||||
jest.mock('@grafana/assistant', () => ({
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn(() => [true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
|
||||
@@ -265,7 +265,7 @@ export const LogListContextProvider = ({
|
||||
const [detailsMode, setDetailsMode] = useState<LogLineDetailsMode>(
|
||||
detailsModeProp ?? getDefaultDetailsMode(containerElement)
|
||||
);
|
||||
const [isAssistantAvailable, openAssistant] = useAssistant();
|
||||
const { isAvailable: isAssistantAvailable, openAssistant } = useAssistant();
|
||||
const [prettifyJSON, setPrettifyJSONState] = useState(prettifyJSONProp);
|
||||
const [wrapLogMessage, setWrapLogMessageState] = useState(wrapLogMessageProp);
|
||||
|
||||
|
||||
@@ -49,7 +49,9 @@ jest.mock('../../utils', () => ({
|
||||
jest.mock('@grafana/assistant', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn().mockReturnValue([true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import { LogListModel } from '../processing';
|
||||
jest.mock('@grafana/assistant', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn().mockReturnValue([true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({
|
||||
isAvailable: true,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"webpack": "5.101.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grafana/assistant": "^0.0.17 || ^0.0.18",
|
||||
"@grafana/assistant": "^0.1.0",
|
||||
"@grafana/runtime": "*"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -61,7 +61,7 @@ jest.mock('@grafana/data', () => ({
|
||||
jest.mock('@grafana/assistant', () => {
|
||||
return {
|
||||
...jest.requireActual('@grafana/assistant'),
|
||||
useAssistant: jest.fn().mockReturnValue([true, jest.fn()]),
|
||||
useAssistant: jest.fn().mockReturnValue({ isAvailable: true }),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -9097,6 +9097,8 @@
|
||||
"make-time-range-permanent": "Make time range absolute/permanent",
|
||||
"move-time-range-back": "Move time range back",
|
||||
"move-time-range-forward": "Move time range forward",
|
||||
"open-alerting": "Go to Alerting",
|
||||
"open-assistant": "Open Assistant",
|
||||
"open-search": "Open search",
|
||||
"open-share-embed-drawer": "Share panel embed",
|
||||
"open-share-link-drawer": "Share panel link",
|
||||
|
||||
@@ -2536,7 +2536,7 @@ __metadata:
|
||||
typescript: "npm:5.9.2"
|
||||
webpack: "npm:5.101.0"
|
||||
peerDependencies:
|
||||
"@grafana/assistant": ^0.0.17 || ^0.0.18
|
||||
"@grafana/assistant": ^0.1.0
|
||||
"@grafana/runtime": "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -2993,9 +2993,9 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/assistant@npm:0.0.18":
|
||||
version: 0.0.18
|
||||
resolution: "@grafana/assistant@npm:0.0.18"
|
||||
"@grafana/assistant@npm:0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "@grafana/assistant@npm:0.1.0"
|
||||
peerDependencies:
|
||||
"@grafana/data": ">=12.1.0"
|
||||
"@grafana/runtime": ">=12.1.0"
|
||||
@@ -3003,7 +3003,7 @@ __metadata:
|
||||
"@grafana/ui": ">=12.1.0"
|
||||
react: ">=18.0.0"
|
||||
rxjs: ">=7.0.0"
|
||||
checksum: 10/6e95f6e7026121f220a20064aa1fc715cf969d93e02b506268dd589d4400b399a6e7d1f2a1a69edb973b604be932c7b091deee1960f90eccf8ea53b67314438e
|
||||
checksum: 10/90cfee9860bc128190ec1357e554685892b3ddd080d914457da311f1d45294dfcc15a427be36c381463eefe39268b5abbc39e6b541ad402e78264cc6d038c23f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3223,7 +3223,7 @@ __metadata:
|
||||
tslib: "npm:2.8.1"
|
||||
typescript: "npm:5.9.2"
|
||||
peerDependencies:
|
||||
"@grafana/assistant": ^0.0.17 || ^0.0.18
|
||||
"@grafana/assistant": ^0.1.0
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
languageName: unknown
|
||||
@@ -18277,7 +18277,7 @@ __metadata:
|
||||
"@formatjs/intl-durationformat": "npm:^0.7.0"
|
||||
"@glideapps/glide-data-grid": "npm:^6.0.0"
|
||||
"@grafana/alerting": "workspace:*"
|
||||
"@grafana/assistant": "npm:0.0.18"
|
||||
"@grafana/assistant": "npm:0.1.0"
|
||||
"@grafana/aws-sdk": "npm:0.7.1"
|
||||
"@grafana/azure-sdk": "npm:0.0.7"
|
||||
"@grafana/data": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user