summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/monitoring
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-07 12:09:13 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-07 12:09:13 +0000
commit211a8c3361ccf4eb92f36edbdcf15c98fcdcc8b7 (patch)
tree0ad37172721a39b0d57240bb1b4e70f200a0d93e /app/assets/javascripts/monitoring
parent456a7247f9e88fc2518b69a1a00e905c6db6d775 (diff)
downloadgitlab-ce-211a8c3361ccf4eb92f36edbdcf15c98fcdcc8b7.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/charts/time_series.vue51
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue16
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue15
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue33
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js22
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js4
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js15
-rw-r--r--app/assets/javascripts/monitoring/utils.js5
9 files changed, 130 insertions, 33 deletions
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index e58d9f23201..8b2c5e44bb5 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -1,5 +1,5 @@
<script>
-import { omit } from 'lodash';
+import { omit, throttle } from 'lodash';
import { GlLink, GlButton, GlTooltip, GlResizeObserverDirective } from '@gitlab/ui';
import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
@@ -18,6 +18,13 @@ import {
import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils';
+const THROTTLED_DATAZOOM_WAIT = 1000; // miliseconds
+const timestampToISODate = timestamp => new Date(timestamp).toISOString();
+
+const events = {
+ datazoom: 'datazoom',
+};
+
export default {
components: {
GlAreaChart,
@@ -98,6 +105,7 @@ export default {
height: chartHeight,
svgs: {},
primaryColor: null,
+ throttledDatazoom: null,
};
},
computed: {
@@ -245,6 +253,11 @@ export default {
this.setSvg('rocket');
this.setSvg('scroll-handle');
},
+ destroyed() {
+ if (this.throttledDatazoom) {
+ this.throttledDatazoom.cancel();
+ }
+ },
methods: {
formatLegendLabel(query) {
return `${query.label}`;
@@ -287,8 +300,39 @@ export default {
console.error('SVG could not be rendered correctly: ', e);
});
},
- onChartUpdated(chart) {
- [this.primaryColor] = chart.getOption().color;
+ onChartUpdated(eChart) {
+ [this.primaryColor] = eChart.getOption().color;
+ },
+
+ onChartCreated(eChart) {
+ // Emit a datazoom event that corresponds to the eChart
+ // `datazoom` event.
+
+ if (this.throttledDatazoom) {
+ // Chart can be created multiple times in this component's
+ // lifetime, remove previous handlers every time
+ // chart is created.
+ this.throttledDatazoom.cancel();
+ }
+
+ // Emitting is throttled to avoid flurries of calls when
+ // the user changes or scrolls the zoom bar.
+ this.throttledDatazoom = throttle(
+ () => {
+ const { startValue, endValue } = eChart.getOption().dataZoom[0];
+ this.$emit(events.datazoom, {
+ start: timestampToISODate(startValue),
+ end: timestampToISODate(endValue),
+ });
+ },
+ THROTTLED_DATAZOOM_WAIT,
+ {
+ leading: false,
+ },
+ );
+
+ eChart.off('datazoom');
+ eChart.on('datazoom', this.throttledDatazoom);
},
onResize() {
if (!this.$refs.chart) return;
@@ -331,6 +375,7 @@ export default {
:height="height"
:average-text="legendAverageText"
:max-text="legendMaxText"
+ @created="onChartCreated"
@updated="onChartUpdated"
>
<template v-if="tooltip.isDeployment">
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 391cd6dd15e..79f32b357fc 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -21,7 +21,6 @@ import createFlash from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
-import { convertToFixedRange } from '~/lib/utils/datetime_range';
import Icon from '~/vue_shared/components/icon.vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
@@ -102,6 +101,11 @@ export default {
type: String,
required: true,
},
+ logsPath: {
+ type: String,
+ required: false,
+ default: invalidUrl,
+ },
defaultBranch: {
type: String,
required: true,
@@ -247,22 +251,20 @@ export default {
dashboardsEndpoint: this.dashboardsEndpoint,
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
+ logsPath: this.logsPath,
});
},
mounted() {
if (!this.hasMetrics) {
this.setGettingStartedEmptyState();
} else {
- const { start, end } = convertToFixedRange(this.selectedTimeRange);
-
- this.fetchData({
- start,
- end,
- });
+ this.setTimeRange(this.selectedTimeRange);
+ this.fetchData();
}
},
methods: {
...mapActions('monitoringDashboard', [
+ 'setTimeRange',
'fetchData',
'setGettingStartedEmptyState',
'setEndpoints',
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index e55de1c0105..49188a7af8f 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -19,14 +19,8 @@ export default {
},
data() {
const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
- const { start, end } = convertToFixedRange(timeRange);
- const params = {
- start,
- end,
- };
-
return {
- params,
+ timeRange: convertToFixedRange(timeRange),
elWidth: 0,
};
},
@@ -49,7 +43,9 @@ export default {
},
mounted() {
this.setInitialState();
- this.fetchMetricsData(this.params);
+ this.setTimeRange(this.timeRange);
+ this.fetchDashboard();
+
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
attributes: true,
@@ -64,7 +60,8 @@ export default {
},
methods: {
...mapActions('monitoringDashboard', [
- 'fetchMetricsData',
+ 'setTimeRange',
+ 'fetchDashboard',
'setEndpoints',
'setFeatureFlags',
'setShowErrorBanner',
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 6751f3d31e8..0258a62b390 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -1,6 +1,7 @@
<script>
import { mapState } from 'vuex';
import { pickBy } from 'lodash';
+import invalidUrl from '~/lib/utils/invalid_url';
import {
GlDropdown,
GlDropdownItem,
@@ -18,7 +19,7 @@ import MonitorColumnChart from './charts/column.vue';
import MonitorStackedColumnChart from './charts/stacked_column.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
-import { downloadCSVOptions, generateLinkToChartOptions } from '../utils';
+import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
export default {
components: {
@@ -58,8 +59,13 @@ export default {
default: 'panel-type-chart',
},
},
+ data() {
+ return {
+ zoomedTimeRange: null,
+ };
+ },
computed: {
- ...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
+ ...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
@@ -70,6 +76,14 @@ export default {
this.graphData.metrics[0].result.length > 0
);
},
+ logsPathWithTimeRange() {
+ const timeRange = this.zoomedTimeRange || this.timeRange;
+
+ if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
+ return timeRangeToUrl(timeRange, this.logsPath);
+ }
+ return null;
+ },
csvText() {
const chartData = this.graphData.metrics[0].result[0].values;
const yLabel = this.graphData.y_label;
@@ -107,6 +121,10 @@ export default {
},
downloadCSVOptions,
generateLinkToChartOptions,
+
+ onDatazoom({ start, end }) {
+ this.zoomedTimeRange = { start, end };
+ },
},
};
</script>
@@ -130,11 +148,13 @@ export default {
<component
:is="monitorChartComponent"
v-else-if="graphDataHasMetrics"
+ ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
+ @datazoom="onDatazoom"
>
<div class="d-flex align-items-center">
<alert-widget
@@ -157,6 +177,15 @@ export default {
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
+
+ <gl-dropdown-item
+ v-if="logsPathWithTimeRange"
+ ref="viewLogsLink"
+ :href="logsPathWithTimeRange"
+ >
+ {{ s__('Metrics|View logs') }}
+ </gl-dropdown-item>
+
<gl-dropdown-item
v-track-event="downloadCSVOptions(graphData.title)"
:href="downloadCsv"
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 29000475bd4..3a052200ab9 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -1,6 +1,7 @@
import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
+import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { gqClient, parseEnvironmentsResponse, removeLeadingSlash } from './utils';
import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
@@ -32,6 +33,10 @@ export const setEndpoints = ({ commit }, endpoints) => {
commit(types.SET_ENDPOINTS, endpoints);
};
+export const setTimeRange = ({ commit }, timeRange) => {
+ commit(types.SET_TIME_RANGE, timeRange);
+};
+
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
@@ -63,19 +68,24 @@ export const receiveEnvironmentsDataSuccess = ({ commit }, data) =>
export const receiveEnvironmentsDataFailure = ({ commit }) =>
commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
-export const fetchData = ({ dispatch }, params) => {
- dispatch('fetchMetricsData', params);
+export const fetchData = ({ dispatch }) => {
+ dispatch('fetchDashboard');
dispatch('fetchDeploymentsData');
dispatch('fetchEnvironmentsData');
};
-export const fetchMetricsData = ({ dispatch }, params) => dispatch('fetchDashboard', params);
-
-export const fetchDashboard = ({ state, dispatch }, params) => {
+export const fetchDashboard = ({ state, dispatch }) => {
dispatch('requestMetricsDashboard');
+ const params = {};
+
+ if (state.timeRange) {
+ const { start, end } = convertToFixedRange(state.timeRange);
+ params.start = start;
+ params.end = end;
+ }
+
if (state.currentDashboard) {
- // eslint-disable-next-line no-param-reassign
params.dashboard = state.currentDashboard;
}
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index bdfaf42b35c..8873142accc 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -14,7 +14,7 @@ export const REQUEST_METRIC_RESULT = 'REQUEST_METRIC_RESULT';
export const RECEIVE_METRIC_RESULT_SUCCESS = 'RECEIVE_METRIC_RESULT_SUCCESS';
export const RECEIVE_METRIC_RESULT_FAILURE = 'RECEIVE_METRIC_RESULT_FAILURE';
-export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
+export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 2a86a6a26d8..5f559290ff7 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -182,6 +182,10 @@ export default {
state.dashboardsEndpoint = endpoints.dashboardsEndpoint;
state.currentDashboard = endpoints.currentDashboard;
state.projectPath = endpoints.projectPath;
+ state.logsPath = endpoints.logsPath || state.logsPath;
+ },
+ [types.SET_TIME_RANGE](state, timeRange) {
+ state.timeRange = timeRange;
},
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.emptyState = 'gettingStarted';
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 9d3227e8aae..a2050f8e893 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -1,22 +1,31 @@
import invalidUrl from '~/lib/utils/invalid_url';
export default () => ({
+ // API endpoints
metricsEndpoint: null,
deploymentsEndpoint: null,
dashboardEndpoint: invalidUrl,
+
+ // Dashboard request parameters
+ timeRange: null,
+ currentDashboard: null,
+
+ // Dashboard data
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
-
dashboard: {
panel_groups: [],
},
+ allDashboards: [],
+ // Other project data
deploymentData: [],
environments: [],
environmentsSearchTerm: '',
environmentsLoading: false,
- allDashboards: [],
- currentDashboard: null,
+
+ // GitLab paths to other pages
projectPath: null,
+ logsPath: invalidUrl,
});
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 915812596c6..b2fa44835e6 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -103,8 +103,9 @@ export const graphDataValidatorForAnomalyValues = graphData => {
/**
* Returns a time range from the current URL params
*
- * @returns {Object} The time range defined by the
- * current URL, reading from `window.location.search`
+ * @returns {Object|null} The time range defined by the
+ * current URL, reading from search query or `window.location.search`.
+ * Returns `null` if no parameters form a time range.
*/
export const timeRangeFromUrl = (search = window.location.search) => {
const params = queryToObject(search);