debian-mirror-gitlab/app/assets/javascripts/monitoring/components/dashboard.vue

520 lines
16 KiB
Vue
Raw Normal View History

2018-03-17 18:26:18 +05:30
<script>
2020-01-01 13:55:28 +05:30
import { mapActions, mapState, mapGetters } from 'vuex';
2019-12-21 20:55:43 +05:30
import VueDraggable from 'vuedraggable';
2020-07-28 23:09:34 +05:30
import Mousetrap from 'mousetrap';
2020-10-24 23:57:45 +05:30
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
2020-06-23 00:09:42 +05:30
import DashboardHeader from './dashboard_header.vue';
2020-05-24 23:13:21 +05:30
import DashboardPanel from './dashboard_panel.vue';
2019-12-26 22:10:19 +05:30
import { s__ } from '~/locale';
2020-10-24 23:57:45 +05:30
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { ESC_KEY } from '~/lib/utils/keys';
2020-06-23 00:09:42 +05:30
import { mergeUrlParams, updateHistory } from '~/lib/utils/url_utility';
2019-09-04 21:01:54 +05:30
import invalidUrl from '~/lib/utils/invalid_url';
2020-03-13 15:44:24 +05:30
import Icon from '~/vue_shared/components/icon.vue';
2018-05-09 12:01:36 +05:30
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
2020-01-01 13:55:28 +05:30
import GroupEmptyState from './group_empty_state.vue';
2020-05-24 23:13:21 +05:30
import VariablesSection from './variables_section.vue';
2020-06-23 00:09:42 +05:30
import LinksSection from './links_section.vue';
2020-03-13 15:44:24 +05:30
2019-12-21 20:55:43 +05:30
import TrackEventDirective from '~/vue_shared/directives/track_event';
2020-05-24 23:13:21 +05:30
import {
timeRangeFromUrl,
panelToUrl,
expandedPanelPayloadFromUrl,
convertVariablesForURL,
} from '../utils';
2020-07-28 23:09:34 +05:30
import { metricStates, keyboardShortcutKeys } from '../constants';
2020-06-23 00:09:42 +05:30
import { defaultTimeRange } from '~/vue_shared/constants';
2019-03-02 22:35:43 +05:30
2018-05-09 12:01:36 +05:30
export default {
components: {
2019-12-21 20:55:43 +05:30
VueDraggable,
2020-06-23 00:09:42 +05:30
DashboardHeader,
2020-05-24 23:13:21 +05:30
DashboardPanel,
2018-11-08 19:23:39 +05:30
Icon,
2020-05-24 23:13:21 +05:30
GlButton,
2020-03-13 15:44:24 +05:30
GraphGroup,
EmptyState,
GroupEmptyState,
2020-05-24 23:13:21 +05:30
VariablesSection,
2020-06-23 00:09:42 +05:30
LinksSection,
2019-07-31 22:56:46 +05:30
},
directives: {
2019-10-12 21:52:04 +05:30
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
2019-12-21 20:55:43 +05:30
TrackEvent: TrackEventDirective,
2018-05-09 12:01:36 +05:30
},
props: {
hasMetrics: {
type: Boolean,
required: false,
default: true,
2018-03-17 18:26:18 +05:30
},
2020-03-13 15:44:24 +05:30
showHeader: {
type: Boolean,
required: false,
default: true,
},
2018-05-09 12:01:36 +05:30
showPanels: {
type: Boolean,
required: false,
default: true,
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
documentationPath: {
type: String,
required: true,
2018-03-17 18:26:18 +05:30
},
2018-05-09 12:01:36 +05:30
settingsPath: {
type: String,
required: true,
},
clustersPath: {
type: String,
required: true,
},
tagsPath: {
type: String,
required: true,
},
2020-03-13 15:44:24 +05:30
defaultBranch: {
2018-05-09 12:01:36 +05:30
type: String,
required: false,
2020-06-23 00:09:42 +05:30
default: '',
2018-05-09 12:01:36 +05:30
},
emptyGettingStartedSvgPath: {
type: String,
required: true,
},
emptyLoadingSvgPath: {
type: String,
required: true,
},
emptyNoDataSvgPath: {
type: String,
required: true,
},
2020-01-01 13:55:28 +05:30
emptyNoDataSmallSvgPath: {
type: String,
required: true,
},
2018-05-09 12:01:36 +05:30
emptyUnableToConnectSvgPath: {
type: String,
required: true,
},
2019-07-31 22:56:46 +05:30
customMetricsAvailable: {
type: Boolean,
required: false,
default: false,
},
customMetricsPath: {
type: String,
2019-09-30 21:07:59 +05:30
required: false,
default: invalidUrl,
2019-07-31 22:56:46 +05:30
},
validateQueryPath: {
type: String,
2019-09-30 21:07:59 +05:30
required: false,
default: invalidUrl,
2019-07-31 22:56:46 +05:30
},
2019-09-30 21:07:59 +05:30
smallEmptyState: {
type: Boolean,
required: false,
default: false,
},
2019-12-04 20:38:33 +05:30
alertsEndpoint: {
type: String,
required: false,
default: null,
},
prometheusAlertsAvailable: {
type: Boolean,
required: false,
default: false,
},
2019-12-21 20:55:43 +05:30
rearrangePanelsAvailable: {
type: Boolean,
required: false,
default: false,
},
2018-05-09 12:01:36 +05:30
},
data() {
return {
2020-03-13 15:44:24 +05:30
selectedTimeRange: timeRangeFromUrl() || defaultTimeRange,
isRearrangingPanels: false,
2020-06-23 00:09:42 +05:30
originalDocumentTitle: document.title,
2020-07-28 23:09:34 +05:30
hoveredPanel: '',
2018-05-09 12:01:36 +05:30
};
},
2019-07-31 22:56:46 +05:30
computed: {
2019-09-04 21:01:54 +05:30
...mapState('monitoringDashboard', [
2019-12-26 22:10:19 +05:30
'dashboard',
2019-09-04 21:01:54 +05:30
'emptyState',
2020-05-24 23:13:21 +05:30
'expandedPanel',
2020-06-23 00:09:42 +05:30
'variables',
'links',
'currentDashboard',
2020-07-28 23:09:34 +05:30
'hasDashboardValidationWarnings',
2020-05-24 23:13:21 +05:30
]),
2020-06-23 00:09:42 +05:30
...mapGetters('monitoringDashboard', ['selectedDashboard', 'getMetricStates']),
2020-07-28 23:09:34 +05:30
shouldShowEmptyState() {
return Boolean(this.emptyState);
},
2020-05-24 23:13:21 +05:30
shouldShowVariablesSection() {
2020-07-28 23:09:34 +05:30
return Boolean(this.variables.length);
2020-06-23 00:09:42 +05:30
},
shouldShowLinksSection() {
return Object.keys(this.links).length > 0;
2020-05-24 23:13:21 +05:30
},
},
watch: {
dashboard(newDashboard) {
try {
const expandedPanel = expandedPanelPayloadFromUrl(newDashboard);
if (expandedPanel) {
this.setExpandedPanel(expandedPanel);
}
} catch {
createFlash(
s__(
'Metrics|Link contains invalid chart information, please verify the link to see the expanded panel.',
),
);
}
},
expandedPanel: {
handler({ group, panel }) {
const dashboardPath = this.currentDashboard || this.selectedDashboard?.path;
updateHistory({
2020-06-23 00:09:42 +05:30
url: panelToUrl(dashboardPath, convertVariablesForURL(this.variables), group, panel),
2020-05-24 23:13:21 +05:30
title: document.title,
});
},
deep: true,
},
2020-06-23 00:09:42 +05:30
selectedDashboard(dashboard) {
this.prependToDocumentTitle(dashboard?.display_name);
},
2020-07-28 23:09:34 +05:30
hasDashboardValidationWarnings(hasWarnings) {
/**
* This watcher is set for future SPA behaviour of the dashboard
*/
if (hasWarnings) {
createFlash(
s__(
'Metrics|Your dashboard schema is invalid. Edit the dashboard to correct the YAML schema.',
),
'warning',
);
}
},
2019-07-31 22:56:46 +05:30
},
2018-05-09 12:01:36 +05:30
created() {
2020-05-24 23:13:21 +05:30
window.addEventListener('keyup', this.onKeyup);
2020-07-28 23:09:34 +05:30
Mousetrap.bind(Object.values(keyboardShortcutKeys), this.runShortcut);
2020-05-24 23:13:21 +05:30
},
destroyed() {
window.removeEventListener('keyup', this.onKeyup);
2020-07-28 23:09:34 +05:30
Mousetrap.unbind(Object.values(keyboardShortcutKeys));
2018-05-09 12:01:36 +05:30
},
mounted() {
if (!this.hasMetrics) {
2019-09-04 21:01:54 +05:30
this.setGettingStartedEmptyState();
2018-05-09 12:01:36 +05:30
} else {
2020-03-13 15:44:24 +05:30
this.setTimeRange(this.selectedTimeRange);
this.fetchData();
2018-05-09 12:01:36 +05:30
}
},
methods: {
2019-09-04 21:01:54 +05:30
...mapActions('monitoringDashboard', [
2020-03-13 15:44:24 +05:30
'setTimeRange',
2019-09-04 21:01:54 +05:30
'fetchData',
'setGettingStartedEmptyState',
2019-12-26 22:10:19 +05:30
'setPanelGroupMetrics',
2020-05-24 23:13:21 +05:30
'setExpandedPanel',
'clearExpandedPanel',
2019-09-04 21:01:54 +05:30
]),
2020-01-01 13:55:28 +05:30
updatePanels(key, panels) {
2019-12-26 22:10:19 +05:30
this.setPanelGroupMetrics({
2020-01-01 13:55:28 +05:30
panels,
2019-12-26 22:10:19 +05:30
key,
});
},
2020-01-01 13:55:28 +05:30
removePanel(key, panels, graphIndex) {
2019-12-26 22:10:19 +05:30
this.setPanelGroupMetrics({
2020-01-01 13:55:28 +05:30
panels: panels.filter((v, i) => i !== graphIndex),
2019-12-26 22:10:19 +05:30
key,
});
},
2020-05-24 23:13:21 +05:30
generatePanelUrl(groupKey, panel) {
const dashboardPath = this.currentDashboard || this.selectedDashboard?.path;
2020-06-23 00:09:42 +05:30
return panelToUrl(dashboardPath, convertVariablesForURL(this.variables), groupKey, panel);
2019-07-31 22:56:46 +05:30
},
2020-01-01 13:55:28 +05:30
/**
* Return a single empty state for a group.
*
* If all states are the same a single state is returned to be displayed
* Except if the state is OK, in which case the group is displayed.
*
* @param {String} groupKey - Identifier for group
* @returns {String} state code from `metricStates`
*/
groupSingleEmptyState(groupKey) {
const states = this.getMetricStates(groupKey);
if (states.length === 1 && states[0] !== metricStates.OK) {
return states[0];
}
return null;
},
2020-07-28 23:09:34 +05:30
/**
* Return true if the entire group is loading.
* @param {String} groupKey - Identifier for group
* @returns {boolean}
*/
isGroupLoading(groupKey) {
return this.groupSingleEmptyState(groupKey) === metricStates.LOADING;
},
2020-01-01 13:55:28 +05:30
/**
* A group should be not collapsed if any metric is loaded (OK)
*
* @param {String} groupKey - Identifier for group
* @returns {Boolean} If the group should be collapsed
*/
collapseGroup(groupKey) {
// Collapse group if no data is available
return !this.getMetricStates(groupKey).includes(metricStates.OK);
},
2020-06-23 00:09:42 +05:30
prependToDocumentTitle(text) {
if (text) {
document.title = `${text} · ${this.originalDocumentTitle}`;
}
2020-04-22 19:07:51 +05:30
},
onTimeRangeZoom({ start, end }) {
updateHistory({
url: mergeUrlParams({ start, end }, window.location.href),
title: document.title,
});
this.selectedTimeRange = { start, end };
2020-06-23 00:09:42 +05:30
// keep the current dashboard time range
// in sync with the Vuex store
this.setTimeRange(this.selectedTimeRange);
2020-04-08 14:13:33 +05:30
},
2020-05-24 23:13:21 +05:30
onExpandPanel(group, panel) {
this.setExpandedPanel({ group, panel });
},
onGoBack() {
this.clearExpandedPanel();
},
onKeyup(event) {
const { key } = event;
2020-10-24 23:57:45 +05:30
if (key === ESC_KEY) {
2020-05-24 23:13:21 +05:30
this.clearExpandedPanel();
}
},
2020-06-23 00:09:42 +05:30
onSetRearrangingPanels(isRearrangingPanels) {
this.isRearrangingPanels = isRearrangingPanels;
},
onDateTimePickerInvalid() {
createFlash(
s__(
'Metrics|Link contains an invalid time window, please verify the link to see the requested time range.',
),
);
// As a fallback, switch to default time range instead
this.selectedTimeRange = defaultTimeRange;
},
2020-07-28 23:09:34 +05:30
isPanelHalfWidth(panelIndex, totalPanels) {
/**
* A single panel on a row should take the full width of its parent.
* All others should have half the width their parent.
*/
const isNumberOfPanelsEven = totalPanels % 2 === 0;
const isLastPanel = panelIndex === totalPanels - 1;
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 = '';
},
2018-05-09 12:01:36 +05:30
},
2020-05-24 23:13:21 +05:30
i18n: {
2020-10-24 23:57:45 +05:30
collapsePanelLabel: s__('Metrics|Collapse panel'),
collapsePanelTooltip: s__('Metrics|Collapse panel (Esc)'),
2020-05-24 23:13:21 +05:30
},
2018-05-09 12:01:36 +05:30
};
2018-03-17 18:26:18 +05:30
</script>
<template>
2020-03-13 15:44:24 +05:30
<div class="prometheus-graphs" data-qa-selector="prometheus_graphs">
2020-06-23 00:09:42 +05:30
<dashboard-header
2020-03-13 15:44:24 +05:30
v-if="showHeader"
ref="prometheusGraphsHeader"
2020-04-22 19:07:51 +05:30
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
2020-06-23 00:09:42 +05:30
:default-branch="defaultBranch"
:rearrange-panels-available="rearrangePanelsAvailable"
:custom-metrics-available="customMetricsAvailable"
:custom-metrics-path="customMetricsPath"
:validate-query-path="validateQueryPath"
:is-rearranging-panels="isRearrangingPanels"
:selected-time-range="selectedTimeRange"
@dateTimePickerInvalid="onDateTimePickerInvalid"
@setRearrangingPanels="onSetRearrangingPanels"
/>
2020-07-28 23:09:34 +05:30
<template v-if="!shouldShowEmptyState">
<variables-section v-if="shouldShowVariablesSection" />
<links-section v-if="shouldShowLinksSection" />
2020-05-24 23:13:21 +05:30
<dashboard-panel
v-show="expandedPanel.panel"
ref="expandedPanel"
:settings-path="settingsPath"
:clipboard-text="generatePanelUrl(expandedPanel.group, expandedPanel.panel)"
:graph-data="expandedPanel.panel"
:alerts-endpoint="alertsEndpoint"
:height="600"
:prometheus-alerts-available="prometheusAlertsAvailable"
@timerangezoom="onTimeRangeZoom"
2019-07-07 11:18:12 +05:30
>
2020-05-24 23:13:21 +05:30
<template #topLeft>
<gl-button
ref="goBackBtn"
v-gl-tooltip
class="mr-3 my-3"
2020-10-24 23:57:45 +05:30
:title="$options.i18n.collapsePanelTooltip"
2020-05-24 23:13:21 +05:30
@click="onGoBack"
>
2020-10-24 23:57:45 +05:30
{{ $options.i18n.collapsePanelLabel }}
2020-05-24 23:13:21 +05:30
</gl-button>
</template>
</dashboard-panel>
<div v-show="!expandedPanel.panel">
<graph-group
v-for="groupData in dashboard.panelGroups"
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
2020-07-28 23:09:34 +05:30
:is-loading="isGroupLoading(groupData.key)"
2020-05-24 23:13:21 +05:30
:collapse-group="collapseGroup(groupData.key)"
2020-03-13 15:44:24 +05:30
>
2020-05-24 23:13:21 +05:30
<vue-draggable
v-if="!groupSingleEmptyState(groupData.key)"
:value="groupData.panels"
group="metrics-dashboard"
:component-data="{ attrs: { class: 'row mx-0 w-100' } }"
:disabled="!isRearrangingPanels"
@input="updatePanels(groupData.key, $event)"
2019-12-21 20:55:43 +05:30
>
2020-05-24 23:13:21 +05:30
<div
v-for="(graphData, graphIndex) in groupData.panels"
:key="`dashboard-panel-${graphIndex}`"
2020-07-28 23:09:34 +05:30
data-testid="dashboard-panel-layout-wrapper"
class="col-12 px-2 mb-2 draggable"
:class="{
'draggable-enabled': isRearrangingPanels,
'col-lg-6': isPanelHalfWidth(graphIndex, groupData.panels.length),
}"
@mouseover="setHoveredPanel(groupData.key, graphIndex)"
@mouseout="clearHoveredPanel"
2020-05-24 23:13:21 +05:30
>
<div class="position-relative draggable-panel js-draggable-panel">
<div
v-if="isRearrangingPanels"
class="draggable-remove js-draggable-remove p-2 w-100 position-absolute d-flex justify-content-end"
@click="removePanel(groupData.key, groupData.panels, graphIndex)"
>
<a class="mx-2 p-2 draggable-remove-link" :aria-label="__('Remove')">
<icon name="close" />
</a>
</div>
2020-03-13 15:44:24 +05:30
2020-05-24 23:13:21 +05:30
<dashboard-panel
2020-07-28 23:09:34 +05:30
:ref="`dashboard-panel-${groupData.key}-${graphIndex}`"
2020-05-24 23:13:21 +05:30
:settings-path="settingsPath"
:clipboard-text="generatePanelUrl(groupData.group, graphData)"
:graph-data="graphData"
:alerts-endpoint="alertsEndpoint"
:prometheus-alerts-available="prometheusAlertsAvailable"
@timerangezoom="onTimeRangeZoom"
@expand="onExpandPanel(groupData.group, graphData)"
/>
</div>
2019-10-12 21:52:04 +05:30
</div>
2020-05-24 23:13:21 +05:30
</vue-draggable>
<div v-else class="py-5 col col-sm-10 col-md-8 col-lg-7 col-xl-6">
<group-empty-state
ref="empty-group"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:selected-state="groupSingleEmptyState(groupData.key)"
:svg-path="emptyNoDataSmallSvgPath"
/>
2020-03-13 15:44:24 +05:30
</div>
2020-05-24 23:13:21 +05:30
</graph-group>
</div>
2020-07-28 23:09:34 +05:30
</template>
2019-09-30 21:07:59 +05:30
<empty-state
v-else
:selected-state="emptyState"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
2020-01-01 13:55:28 +05:30
:empty-no-data-small-svg-path="emptyNoDataSmallSvgPath"
2019-09-30 21:07:59 +05:30
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
:compact="smallEmptyState"
/>
2018-03-17 18:26:18 +05:30
</div>
</template>