From bf39d9de84a8f032a6857c0bb9e654ceda8dab29 Mon Sep 17 00:00:00 2001 From: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:28:52 +0530 Subject: [PATCH] fix: keyboard shortcuts for metrics (#13413) --- .../vue-shortcut-manager/shortcutRegistry.ts | 7 +- web/src/locales/languages/en-US.json | 2 + web/src/plugins/metrics/Index.vue | 33 ++++++++++ .../metrics/explorer/MetricsExplorer.vue | 66 +++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) diff --git a/web/src/lib/vue-shortcut-manager/shortcutRegistry.ts b/web/src/lib/vue-shortcut-manager/shortcutRegistry.ts index 339faa4094..c9de818e99 100644 --- a/web/src/lib/vue-shortcut-manager/shortcutRegistry.ts +++ b/web/src/lib/vue-shortcut-manager/shortcutRegistry.ts @@ -249,8 +249,11 @@ export const SHORTCUT_REGISTRY: ShortcutGroup[] = [ pageKey: "shortcuts.pages.metrics", scope: "metrics", shortcuts: [ - { id: "metricsRunQuery", keyForWindows: "ctrl+enter", keyForMac: "meta+enter", descriptionKey: "shortcuts.actions.metricsRunQuery" }, - { id: "metricsRefresh", key: "r", descriptionKey: "shortcuts.actions.metricsRefresh" }, + { id: "metricsRunQuery", keyForWindows: "ctrl+enter", keyForMac: "meta+enter", descriptionKey: "shortcuts.actions.metricsRunQuery" }, + { id: "metricsRefresh", key: "r", descriptionKey: "shortcuts.actions.metricsRefresh" }, + { id: "metricsFocusQuery", key: "/", descriptionKey: "shortcuts.actions.focusQuery" }, + { id: "metricsAddToDashboard", key: "d", descriptionKey: "shortcuts.actions.metricsAddToDashboard" }, + { id: "metricsCopyUrl", key: "c", descriptionKey: "shortcuts.actions.metricsCopyUrl" }, ], }, diff --git a/web/src/locales/languages/en-US.json b/web/src/locales/languages/en-US.json index 5318b97c37..1da188fdcb 100644 --- a/web/src/locales/languages/en-US.json +++ b/web/src/locales/languages/en-US.json @@ -10224,6 +10224,8 @@ "panelEditorQueryInspector": "Open query inspector", "metricsRunQuery": "Run PromQL query", "metricsRefresh": "Refresh", + "metricsAddToDashboard": "Add to dashboard", + "metricsCopyUrl": "Copy share link", "tracesSearch": "Run query", "tracesRefresh": "Refresh", "alertsCreate": "Create new alert", diff --git a/web/src/plugins/metrics/Index.vue b/web/src/plugins/metrics/Index.vue index 357e60d424..f315a0819f 100644 --- a/web/src/plugins/metrics/Index.vue +++ b/web/src/plugins/metrics/Index.vue @@ -61,6 +61,7 @@ along with this program. If not, see . variant="outline" size="icon-toolbar" data-test="metrics-share-btn" + shortcut-id="metricsCopyUrl" class="h-8" /> @@ -166,6 +171,7 @@ const AddToDashboard = defineAsyncComponent(() => { return import("./../metrics/AddToDashboard.vue"); }); import OButton from "@/lib/core/Button/OButton.vue"; +import OTooltip from "@/lib/overlay/Tooltip/OTooltip.vue"; import { useShortcuts } from "@/lib/vue-shortcut-manager"; import { isInputFocused } from "@/utils/keyboardShortcuts"; @@ -182,6 +188,7 @@ export default defineComponent({ AutoRefreshInterval, PanelEditor, OButton, + OTooltip, ShareButton, }, setup() { @@ -634,6 +641,32 @@ export default defineComponent({ runQuery(); }, }, + { + id: "metricsFocusQuery", + handler: () => { + // The metrics PromQL editor is Monaco — focus its inner textarea. + const el = document.querySelector( + '[data-test="dashboard-panel-query-editor"] textarea, [data-test="dashboard-panel-query-editor"] .monaco-editor textarea, [data-test="dashboard-panel-query-editor"] .cm-editor', + ); + el?.focus(); + }, + }, + { + id: "metricsAddToDashboard", + handler: () => { + if (isInputFocused()) return; + addToDashboard(); + }, + }, + { + id: "metricsCopyUrl", + handler: () => { + // Reuse ShareButton's short-URL + clipboard + toast flow. + document + .querySelector('[data-test="metrics-share-btn"]') + ?.click(); + }, + }, ]); return { diff --git a/web/src/plugins/metrics/explorer/MetricsExplorer.vue b/web/src/plugins/metrics/explorer/MetricsExplorer.vue index 896b386eb6..aa6b1f10a2 100644 --- a/web/src/plugins/metrics/explorer/MetricsExplorer.vue +++ b/web/src/plugins/metrics/explorer/MetricsExplorer.vue @@ -111,6 +111,10 @@ along with this program. If not, see . @click="() => onRefresh()" > {{ t("metrics.explorer.refresh") }} + . variant="outline" size="icon-toolbar" data-test="metrics-explorer-share-btn" + shortcut-id="metricsCopyUrl" /> @@ -399,6 +404,7 @@ along with this program. If not, see . count: grid.favorites.value.length, }) " + shortcut-id="metricsAddToDashboard" /> @@ -631,6 +637,8 @@ import { } from "@/utils/dashboard/urlTimeParams"; import type { MetricCard as MetricCardModel } from "@/utils/metrics/metricFamily"; import segment from "@/services/segment_analytics"; +import { useShortcuts } from "@/lib/vue-shortcut-manager"; +import { isInputFocused } from "@/utils/keyboardShortcuts"; /** * Card min width; drives the responsive column count. @@ -1740,6 +1748,64 @@ export default defineComponent({ }, ); + // ── Keyboard shortcuts ──────────────────────────────────────────────── + // Registered here (not just in the editor Index.vue) because `/metrics` + // mounts THIS explorer — the editor lives at `/metrics/editor`. Keys and + // scope come from the "metrics" group in shortcutRegistry.ts. + useShortcuts([ + { + // ⌘/Ctrl+Enter — run the query. In Visualize this re-runs the chart; + // in Explore/Workspace it refreshes the grid (there is no single query). + id: "metricsRunQuery", + handler: () => onRefresh({ manual: true }), + }, + { + id: "metricsRefresh", + handler: () => { + if (isInputFocused()) return; + onRefresh({ manual: true }); + }, + }, + { + id: "metricsFocusQuery", + handler: () => { + // Grid modes: the metric search box. Visualize: the PromQL editor. + const el = document.querySelector( + '[data-test="metrics-explorer-search"] input, [data-test="metrics-explorer-search"] textarea, [data-test="dashboard-panel-query-editor"] textarea, [data-test="dashboard-panel-query-editor"] .monaco-editor textarea', + ); + el?.focus(); + }, + }, + { + id: "metricsAddToDashboard", + handler: () => { + if (isInputFocused()) return; + // Visualize: add the single chart you built (validates first, then + // opens the add-to-dashboard dialog). Explore/Workspace: convert the + // pinned favourites into panels. In Explore with no favourites there + // is nothing to add, so this is a no-op — same as the toolbar, which + // shows the convert button only in Workspace with favourites. + if (mode.value === "visualize") { + visualizeRef.value?.onAddToDashboard?.(); + } else { + openConvertToDashboard(); + } + }, + }, + { + id: "metricsCopyUrl", + handler: () => { + if (isInputFocused()) return; + // Reuse ShareButton's short-URL + clipboard + toast flow. + document + .querySelector( + '[data-test="metrics-explorer-share-btn"]', + ) + ?.click(); + }, + }, + ]); + return { t, store,