Commit a9e1da31 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas Committed by Phil Hughes

Add keyboard shortcuts to metrics dashboard

This adds keyboard shortcuts to the actions
dropdown, said shortcuts will not be displayed
for this iteration until there's a consistent
design for displaying said shortcuts
parent 1c9315e3
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import VueDraggable from 'vuedraggable';
import Mousetrap from 'mousetrap';
import { GlIcon, GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import DashboardHeader from './dashboard_header.vue';
import DashboardPanel from './dashboard_panel.vue';
......@@ -24,7 +25,7 @@ import {
expandedPanelPayloadFromUrl,
convertVariablesForURL,
} from '../utils';
import { metricStates } from '../constants';
import { metricStates, keyboardShortcutKeys } from '../constants';
import { defaultTimeRange } from '~/vue_shared/constants';
export default {
......@@ -149,6 +150,7 @@ export default {
selectedTimeRange: timeRangeFromUrl() || defaultTimeRange,
isRearrangingPanels: false,
originalDocumentTitle: document.title,
hoveredPanel: '',
};
},
computed: {
......@@ -214,9 +216,13 @@ export default {
},
created() {
window.addEventListener('keyup', this.onKeyup);
Mousetrap.bind(Object.values(keyboardShortcutKeys), this.runShortcut);
},
destroyed() {
window.removeEventListener('keyup', this.onKeyup);
Mousetrap.unbind(Object.values(keyboardShortcutKeys));
},
mounted() {
if (!this.hasMetrics) {
......@@ -326,6 +332,56 @@ export default {
return isNumberOfPanelsEven || !isLastPanel;
},
/**
* TODO: Investigate this to utilize the eventBus from Vue
* The intentation behind this cleanup is to allow for better tests
* as well as use the correct eventBus facilities that are compatible
* with Vue 3
* https://gitlab.com/gitlab-org/gitlab/-/issues/225583
*/
//
runShortcut(e) {
const panel = this.$refs[this.hoveredPanel];
if (!panel) return;
const [panelInstance] = panel;
let actionToRun = '';
switch (e.key) {
case keyboardShortcutKeys.EXPAND:
actionToRun = 'onExpandFromKeyboardShortcut';
break;
case keyboardShortcutKeys.VISIT_LOGS:
actionToRun = 'visitLogsPageFromKeyboardShortcut';
break;
case keyboardShortcutKeys.SHOW_ALERT:
actionToRun = 'showAlertModalFromKeyboardShortcut';
break;
case keyboardShortcutKeys.DOWNLOAD_CSV:
actionToRun = 'downloadCsvFromKeyboardShortcut';
break;
case keyboardShortcutKeys.CHART_COPY:
actionToRun = 'copyChartLinkFromKeyboardShotcut';
break;
default:
actionToRun = 'onExpandFromKeyboardShortcut';
break;
}
panelInstance[actionToRun]();
},
setHoveredPanel(groupKey, graphIndex) {
this.hoveredPanel = `dashboard-panel-${groupKey}-${graphIndex}`;
},
clearHoveredPanel() {
this.hoveredPanel = '';
},
},
i18n: {
goBackLabel: s__('Metrics|Go back (Esc)'),
......@@ -407,6 +463,8 @@ export default {
'draggable-enabled': isRearrangingPanels,
'col-lg-6': isPanelHalfWidth(graphIndex, groupData.panels.length),
}"
@mouseover="setHoveredPanel(groupData.key, graphIndex)"
@mouseout="clearHoveredPanel"
>
<div class="position-relative draggable-panel js-draggable-panel">
<div
......@@ -420,6 +478,7 @@ export default {
</div>
<dashboard-panel
:ref="`dashboard-panel-${groupData.key}-${graphIndex}`"
:settings-path="settingsPath"
:clipboard-text="generatePanelUrl(groupData.group, graphData)"
:graph-data="graphData"
......
......@@ -2,6 +2,7 @@
import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
import {
GlResizeObserverDirective,
GlIcon,
......@@ -29,7 +30,6 @@ import MonitorStackedColumnChart from './charts/stacked_column.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import AlertWidget from './alert_widget.vue';
import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
import { isSafeURL } from '~/lib/utils/url_utility';
const events = {
timeRangeZoom: 'timerangezoom',
......@@ -244,6 +244,9 @@ export default {
this.hasMetricsInDb
);
},
alertModalId() {
return `alert-modal-${this.graphData.id}`;
},
},
mounted() {
this.refreshTitleTooltip();
......@@ -282,6 +285,11 @@ export default {
onExpand() {
this.$emit(events.expand);
},
onExpandFromKeyboardShortcut() {
if (this.isContextualMenuShown) {
this.onExpand();
}
},
setAlerts(alertPath, alertAttributes) {
if (alertAttributes) {
this.$set(this.allAlerts, alertPath, alertAttributes);
......@@ -292,6 +300,34 @@ export default {
safeUrl(url) {
return isSafeURL(url) ? url : '#';
},
showAlertModal() {
this.$root.$emit('bv::show::modal', this.alertModalId);
},
showAlertModalFromKeyboardShortcut() {
if (this.isContextualMenuShown) {
this.showAlertModal();
}
},
visitLogsPage() {
if (this.logsPathWithTimeRange) {
visitUrl(relativePathToAbsolute(this.logsPathWithTimeRange, getBaseURL()));
}
},
visitLogsPageFromKeyboardShortcut() {
if (this.isContextualMenuShown) {
this.visitLogsPage();
}
},
downloadCsvFromKeyboardShortcut() {
if (this.csvText && this.isContextualMenuShown) {
this.$refs.downloadCsvLink.$el.firstChild.click();
}
},
copyChartLinkFromKeyboardShotcut() {
if (this.clipboardText && this.isContextualMenuShown) {
this.$refs.copyChartLink.$el.firstChild.click();
}
},
},
panelTypes,
};
......@@ -313,7 +349,7 @@ export default {
<alert-widget
v-if="isContextualMenuShown && alertWidgetAvailable"
class="mx-1"
:modal-id="`alert-modal-${graphData.id}`"
:modal-id="alertModalId"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
......@@ -328,7 +364,7 @@ export default {
ref="contextualMenu"
data-qa-selector="prometheus_graph_widgets"
>
<div class="d-flex align-items-center">
<div data-testid="dropdown-wrapper" class="d-flex align-items-center">
<gl-dropdown
v-gl-tooltip
toggle-class="shadow-none border-0"
......@@ -383,7 +419,7 @@ export default {
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${graphData.id}`"
v-gl-modal="alertModalId"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
......
......@@ -251,3 +251,17 @@ export const VARIABLE_TYPES = {
* before passing the data to the backend.
*/
export const VARIABLE_PREFIX = 'var-';
/**
* All of the actions inside each panel dropdown can be accessed
* via keyboard shortcuts than can be activated via mouse hovers
* and or focus via tabs.
*/
export const keyboardShortcutKeys = {
EXPAND: 'e',
VISIT_LOGS: 'l',
SHOW_ALERT: 'a',
DOWNLOAD_CSV: 'd',
CHART_COPY: 'c',
};
---
title: Add keyboard shortcuts to metrics dashboard
merge_request: 32804
author:
type: added
......@@ -1168,6 +1168,34 @@ describe('Dashboard', () => {
});
});
describe('keyboard shortcuts', () => {
const currentDashboard = dashboardGitResponse[1].path;
const panelRef = 'dashboard-panel-response-metrics-aws-elb-4-1'; // skip expanded panel
// While the recommendation in the documentation is to test
// with a data-testid attribute, I want to make sure that
// the dashboard panels have a ref attribute set.
const getDashboardPanel = () => wrapper.find({ ref: panelRef });
beforeEach(() => {
setupStoreWithData(store);
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard,
});
createShallowWrapper({ hasMetrics: true });
wrapper.setData({ hoveredPanel: panelRef });
return wrapper.vm.$nextTick();
});
it('contains a ref attribute inside a DashboardPanel component', () => {
const dashboardPanel = getDashboardPanel();
expect(dashboardPanel.exists()).toBe(true);
});
});
describe('add custom metrics', () => {
const findAddMetricButton = () => wrapper.find(DashboardHeader).find({ ref: 'addMetricBtn' });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment