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"
/>
.
@click="runQuery"
>
{{ t("metrics.runQuery") }}
+
@@ -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,