summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/monitoring
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-11 09:08:12 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-11 09:08:12 +0000
commit6b8040dc25fdc5fe614c3796a147517dd50bc7d8 (patch)
tree1930c21748fc632a7900659a71fcb7248097879f /app/assets/javascripts/monitoring
parent7b875aa3fd1645e2e881997256ba94c6cb73ab3d (diff)
downloadgitlab-ce-6b8040dc25fdc5fe614c3796a147517dd50bc7d8.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/monitoring')
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue99
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue7
-rw-r--r--app/assets/javascripts/monitoring/components/empty_state.vue9
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue29
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js38
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js32
-rw-r--r--app/assets/javascripts/monitoring/stores/index.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
9 files changed, 149 insertions, 69 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index fb6f5dc73b8..2a9321f6733 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import VueDraggable from 'vuedraggable';
import {
GlButton,
@@ -99,6 +99,10 @@ export default {
type: String,
required: true,
},
+ emptyNoDataSmallSvgPath: {
+ type: String,
+ required: true,
+ },
emptyUnableToConnectSvgPath: {
type: String,
required: true,
@@ -176,11 +180,11 @@ export default {
'showEmptyState',
'environments',
'deploymentData',
- 'metricsWithData',
'useDashboardEndpoint',
'allDashboards',
'additionalPanelTypesEnabled',
]),
+ ...mapGetters('monitoringDashboard', ['metricsWithData']),
firstDashboard() {
return this.environmentsEndpoint.length > 0 && this.allDashboards.length > 0
? this.allDashboards[0]
@@ -280,13 +284,8 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
- chartsWithData(panels) {
- return panels.filter(panel =>
- panel.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
- );
- },
groupHasData(group) {
- return this.chartsWithData(group.panels).length > 0;
+ return this.metricsWithData(group.key).length > 0;
},
onDateTimePickerApply(timeWindowUrlParams) {
return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
@@ -447,42 +446,61 @@ export default {
:key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
- :collapse-group="groupHasData(groupData)"
+ :collapse-group="!groupHasData(groupData)"
>
- <vue-draggable
- :value="groupData.panels"
- group="metrics-dashboard"
- :component-data="{ attrs: { class: 'row mx-0 w-100' } }"
- :disabled="!isRearrangingPanels"
- @input="updatePanels(groupData.key, $event)"
- >
- <div
- v-for="(graphData, graphIndex) in groupData.panels"
- :key="`panel-type-${graphIndex}`"
- class="col-12 col-lg-6 px-2 mb-2 draggable"
- :class="{ 'draggable-enabled': isRearrangingPanels }"
+ <div v-if="groupHasData(groupData)">
+ <vue-draggable
+ :value="groupData.panels"
+ group="metrics-dashboard"
+ :component-data="{ attrs: { class: 'row mx-0 w-100' } }"
+ :disabled="!isRearrangingPanels"
+ @input="updatePanels(groupData.key, $event)"
>
- <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>
+ <div
+ v-for="(graphData, graphIndex) in groupData.panels"
+ :key="`panel-type-${graphIndex}`"
+ class="col-12 col-lg-6 px-2 mb-2 draggable"
+ :class="{ 'draggable-enabled': isRearrangingPanels }"
+ >
+ <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>
- <panel-type
- :clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
- :graph-data="graphData"
- :alerts-endpoint="alertsEndpoint"
- :prometheus-alerts-available="prometheusAlertsAvailable"
- :index="`${index}-${graphIndex}`"
- />
+ <panel-type
+ :clipboard-text="
+ generateLink(groupData.group, graphData.title, graphData.y_label)
+ "
+ :graph-data="graphData"
+ :alerts-endpoint="alertsEndpoint"
+ :prometheus-alerts-available="prometheusAlertsAvailable"
+ :index="`${index}-${graphIndex}`"
+ />
+ </div>
</div>
- </div>
- </vue-draggable>
+ </vue-draggable>
+ </div>
+ <div v-else class="py-5 col col-sm-10 col-md-8 col-lg-7 col-xl-6">
+ <empty-state
+ ref="empty-group"
+ selected-state="noDataGroup"
+ :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"
+ :empty-no-data-small-svg-path="emptyNoDataSmallSvgPath"
+ :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
+ :compact="true"
+ />
+ </div>
</graph-group>
</div>
<empty-state
@@ -494,6 +512,7 @@ export default {
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-no-data-svg-path="emptyNoDataSvgPath"
+ :empty-no-data-small-svg-path="emptyNoDataSmallSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
:compact="smallEmptyState"
/>
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index a5c933a0071..dae1fbad547 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -1,5 +1,5 @@
<script>
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } from 'vuex';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import GraphGroup from './graph_group.vue';
@@ -35,7 +35,8 @@ export default {
};
},
computed: {
- ...mapState('monitoringDashboard', ['dashboard', 'metricsWithData']),
+ ...mapState('monitoringDashboard', ['dashboard']),
+ ...mapGetters('monitoringDashboard', ['metricsWithData']),
charts() {
if (!this.dashboard || !this.dashboard.panel_groups) {
return [];
@@ -73,7 +74,7 @@ export default {
'setShowErrorBanner',
]),
chartHasData(chart) {
- return chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id));
+ return chart.metrics.some(metric => this.metricsWithData().includes(metric.metric_id));
},
onSidebarMutation() {
setTimeout(() => {
diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue
index 1bb40447a3e..ab8c9712ce4 100644
--- a/app/assets/javascripts/monitoring/components/empty_state.vue
+++ b/app/assets/javascripts/monitoring/components/empty_state.vue
@@ -37,6 +37,10 @@ export default {
type: String,
required: true,
},
+ emptyNoDataSmallSvgPath: {
+ type: String,
+ required: true,
+ },
emptyUnableToConnectSvgPath: {
type: String,
required: true,
@@ -80,6 +84,11 @@ export default {
secondaryButtonText: '',
secondaryButtonPath: '',
},
+ noDataGroup: {
+ svgUrl: this.emptyNoDataSmallSvgPath,
+ title: __('No data to display'),
+ description: __('The data source is connected, but there is no data to display.'),
+ },
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: __('Unable to connect to Prometheus server'),
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index e01324372a7..5a7981b6534 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -15,31 +15,44 @@ export default {
required: false,
default: true,
},
+ /**
+ * Initial value of collapse on mount.
+ */
collapseGroup: {
type: Boolean,
- required: true,
+ required: false,
+ default: false,
},
},
data() {
return {
- showGroup: true,
+ isCollapsed: this.collapseGroup,
};
},
computed: {
caretIcon() {
- return this.collapseGroup && this.showGroup ? 'angle-down' : 'angle-right';
+ return this.isCollapsed ? 'angle-right' : 'angle-down';
+ },
+ },
+ watch: {
+ collapseGroup(val) {
+ // Respond to changes in collapseGroup but do not
+ // collapse it once was opened by the user.
+ if (this.showPanels && !val) {
+ this.isCollapsed = false;
+ }
},
},
methods: {
collapse() {
- this.showGroup = !this.showGroup;
+ this.isCollapsed = !this.isCollapsed;
},
},
};
</script>
<template>
- <div v-if="showPanels" class="card prometheus-panel">
+ <div v-if="showPanels" ref="graph-group" class="card prometheus-panel">
<div class="card-header d-flex align-items-center">
<h4 class="flex-grow-1">{{ name }}</h4>
<a role="button" class="js-graph-group-toggle" @click="collapse">
@@ -47,12 +60,12 @@ export default {
</a>
</div>
<div
- v-if="collapseGroup"
- v-show="collapseGroup && showGroup"
+ v-show="!isCollapsed"
+ ref="graph-group-content"
class="card-body prometheus-graph-group p-0"
>
<slot></slot>
</div>
</div>
- <div v-else class="prometheus-graph-group"><slot></slot></div>
+ <div v-else ref="graph-group-content" class="prometheus-graph-group"><slot></slot></div>
</template>
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 3612e4d173f..268d9d636b1 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -4,7 +4,7 @@ import createFlash from '~/flash';
import trackDashboardLoad from '../monitoring_tracking_helper';
import statusCodes from '../../lib/utils/http_status';
import { backOff } from '../../lib/utils/common_utils';
-import { s__ } from '../../locale';
+import { s__, sprintf } from '../../locale';
const TWO_MINUTES = 120000;
@@ -74,17 +74,21 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
return backOffRequest(() => axios.get(state.dashboardEndpoint, { params }))
.then(resp => resp.data)
.then(response => dispatch('receiveMetricsDashboardSuccess', { response, params }))
- .then(() => {
- const dashboardType = state.currentDashboard === '' ? 'default' : 'custom';
- return trackDashboardLoad({
- label: `${dashboardType}_metrics_dashboard`,
- value: state.metricsWithData.length,
- });
- })
- .catch(error => {
- dispatch('receiveMetricsDashboardFailure', error);
- if (state.setShowErrorBanner) {
- createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ .catch(e => {
+ dispatch('receiveMetricsDashboardFailure', e);
+ if (state.showErrorBanner) {
+ if (e.response.data && e.response.data.message) {
+ const { message } = e.response.data;
+ createFlash(
+ sprintf(
+ s__('Metrics|There was an error while retrieving metrics. %{message}'),
+ { message },
+ false,
+ ),
+ );
+ } else {
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ }
}
});
};
@@ -126,7 +130,7 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
});
};
-export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
+export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, params) => {
commit(types.REQUEST_METRICS_DATA);
const promises = [];
@@ -140,9 +144,11 @@ export const fetchPrometheusMetrics = ({ state, commit, dispatch }, params) => {
return Promise.all(promises)
.then(() => {
- if (state.metricsWithData.length === 0) {
- commit(types.SET_NO_DATA_EMPTY_STATE);
- }
+ const dashboardType = state.currentDashboard === '' ? 'default' : 'custom';
+ trackDashboardLoad({
+ label: `${dashboardType}_metrics_dashboard`,
+ value: getters.metricsWithData().length,
+ });
})
.catch(() => {
createFlash(s__(`Metrics|There was an error while retrieving metrics`), 'warning');
diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js
new file mode 100644
index 00000000000..3eddd52705d
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/getters.js
@@ -0,0 +1,32 @@
+const metricsIdsInPanel = panel =>
+ panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
+
+/**
+ * Getter to obtain the list of metric ids that have data
+ *
+ * Useful to understand which parts of the dashboard should
+ * be displayed. It is a Vuex Method-Style Access getter.
+ *
+ * @param {Object} state
+ * @returns {Function} A function that returns an array of
+ * metrics in the dashboard that contain results, optionally
+ * filtered by group key.
+ */
+export const metricsWithData = state => groupKey => {
+ let groups = state.dashboard.panel_groups;
+ if (groupKey) {
+ groups = groups.filter(group => group.key === groupKey);
+ }
+
+ const res = [];
+ groups.forEach(group => {
+ group.panels.forEach(panel => {
+ res.push(...metricsIdsInPanel(panel));
+ });
+ });
+
+ return res;
+};
+
+// prevent babel-plugin-rewire from generating an invalid default during karma tests
+export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/index.js b/app/assets/javascripts/monitoring/stores/index.js
index d58398c54ae..c1c466b7cf0 100644
--- a/app/assets/javascripts/monitoring/stores/index.js
+++ b/app/assets/javascripts/monitoring/stores/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
+import * as getters from './getters';
import mutations from './mutations';
import state from './state';
@@ -12,6 +13,7 @@ export const createStore = () =>
monitoringDashboard: {
namespaced: true,
actions,
+ getters,
mutations,
state,
},
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index bfa76aa7cea..db5ec4e9e2b 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -67,7 +67,6 @@ export default {
group.panels.forEach(panel => {
panel.metrics.forEach(metric => {
if (metric.metric_id === metricId) {
- state.metricsWithData.push(metricId);
// ensure dates/numbers are correctly formatted for charts
const normalizedResults = result.map(normalizeQueryResult);
Vue.set(metric, 'result', Object.freeze(normalizedResults));
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index e3300967022..88f333aeb80 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -13,7 +13,6 @@ export default () => ({
},
deploymentData: [],
environments: [],
- metricsWithData: [],
allDashboards: [],
currentDashboard: null,
projectPath: null,