diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-13 12:07:41 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-13 12:07:41 +0000 |
commit | 8cc5f2790908ba9bb8eecba2b78a3c5a88c77b90 (patch) | |
tree | 2d6211503a5111d43a9edce0c56b94fd1b347e1b /app/assets/javascripts/monitoring | |
parent | 17b91a3c6ab73fff087e91665e9afb8046cbf045 (diff) | |
download | gitlab-ce-8cc5f2790908ba9bb8eecba2b78a3c5a88c77b90.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/monitoring')
5 files changed, 167 insertions, 21 deletions
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 1a1fcdd0e66..e613351e524 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -1,5 +1,42 @@ import { __ } from '~/locale'; +export const PROMETHEUS_TIMEOUT = 120000; // TWO_MINUTES + +/** + * Errors in Prometheus Queries (PromQL) for metrics + */ +export const metricsErrors = { + /** + * Connection timed out to prometheus server + * the timeout is set to PROMETHEUS_TIMEOUT + * + */ + TIMEOUT: 'TIMEOUT', + + /** + * The prometheus server replies with an empty data set + */ + NO_DATA: 'NO_DATA', + + /** + * The prometheus server cannot be reached + */ + CONNECTION_FAILED: 'CONNECTION_FAILED', + + /** + * The prometheus server was reach but it cannot process + * the query. This can happen for several reasons: + * - PromQL syntax is incorrect + * - An operator is not supported + */ + BAD_DATA: 'BAD_DATA', + + /** + * No specific reason found for error + */ + UNKNOWN_ERROR: 'UNKNOWN_ERROR', +}; + export const sidebarAnimationDuration = 300; // milliseconds. export const chartHeight = 300; diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 268d9d636b1..a655191b2b4 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -6,7 +6,7 @@ import statusCodes from '../../lib/utils/http_status'; import { backOff } from '../../lib/utils/common_utils'; import { s__, sprintf } from '../../locale'; -const TWO_MINUTES = 120000; +import { PROMETHEUS_TIMEOUT } from '../constants'; function backOffRequest(makeRequestCallback) { return backOff((next, stop) => { @@ -19,7 +19,7 @@ function backOffRequest(makeRequestCallback) { } }) .catch(stop); - }, TWO_MINUTES); + }, PROMETHEUS_TIMEOUT); } export const setGettingStartedEmptyState = ({ commit }) => { @@ -125,9 +125,17 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { step, }; - return fetchPrometheusResult(metric.prometheus_endpoint_path, queryParams).then(result => { - commit(types.SET_QUERY_RESULT, { metricId: metric.metric_id, result }); - }); + commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metric_id }); + + return fetchPrometheusResult(metric.prometheus_endpoint_path, queryParams) + .then(result => { + commit(types.RECEIVE_METRIC_RESULT_SUCCESS, { metricId: metric.metric_id, result }); + }) + .catch(error => { + commit(types.RECEIVE_METRIC_RESULT_ERROR, { metricId: metric.metric_id, error }); + // Continue to throw error so the dashboard can notify using createFlash + throw error; + }); }; export const fetchPrometheusMetrics = ({ state, commit, dispatch, getters }, params) => { @@ -159,7 +167,8 @@ export const fetchDeploymentsData = ({ state, dispatch }) => { if (!state.deploymentsEndpoint) { return Promise.resolve([]); } - return backOffRequest(() => axios.get(state.deploymentsEndpoint)) + return axios + .get(state.deploymentsEndpoint) .then(resp => resp.data) .then(response => { if (!response || !response.deployments) { diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js index fa15a2ba800..e4e467f3d68 100644 --- a/app/assets/javascripts/monitoring/stores/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/mutation_types.js @@ -1,13 +1,19 @@ export const REQUEST_METRICS_DATA = 'REQUEST_METRICS_DATA'; export const RECEIVE_METRICS_DATA_SUCCESS = 'RECEIVE_METRICS_DATA_SUCCESS'; export const RECEIVE_METRICS_DATA_FAILURE = 'RECEIVE_METRICS_DATA_FAILURE'; + export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA'; export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS'; export const RECEIVE_DEPLOYMENTS_DATA_FAILURE = 'RECEIVE_DEPLOYMENTS_DATA_FAILURE'; + export const REQUEST_ENVIRONMENTS_DATA = 'REQUEST_ENVIRONMENTS_DATA'; export const RECEIVE_ENVIRONMENTS_DATA_SUCCESS = 'RECEIVE_ENVIRONMENTS_DATA_SUCCESS'; export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAILURE'; -export const SET_QUERY_RESULT = 'SET_QUERY_RESULT'; + +export const REQUEST_METRIC_RESULT = 'REQUEST_METRIC_RESULT'; +export const RECEIVE_METRIC_RESULT_SUCCESS = 'RECEIVE_METRIC_RESULT_SUCCESS'; +export const RECEIVE_METRIC_RESULT_ERROR = 'RECEIVE_METRIC_RESULT_ERROR'; + export const SET_TIME_WINDOW = 'SET_TIME_WINDOW'; export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS'; export const SET_ENDPOINTS = 'SET_ENDPOINTS'; diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index db5ec4e9e2b..f04c12c2ac8 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -2,6 +2,9 @@ import Vue from 'vue'; import { slugify } from '~/lib/utils/text_utility'; import * as types from './mutation_types'; import { normalizeMetric, normalizeQueryResult } from './utils'; +import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils'; +import { metricsErrors } from '../constants'; +import httpStatusCodes from '~/lib/utils/http_status'; const normalizePanelMetrics = (metrics, defaultLabel) => metrics.map(metric => ({ @@ -9,7 +12,74 @@ const normalizePanelMetrics = (metrics, defaultLabel) => label: metric.label || defaultLabel, })); +/** + * Locate and return a metric in the dashboard by its id + * as generated by `uniqMetricsId()`. + * @param {String} metricId Unique id in the dashboard + * @param {Object} dashboard Full dashboard object + */ +const findMetricInDashboard = (metricId, dashboard) => { + let res = null; + dashboard.panel_groups.forEach(group => { + group.panels.forEach(panel => { + panel.metrics.forEach(metric => { + if (metric.metric_id === metricId) { + res = metric; + } + }); + }); + }); + return res; +}; + +/** + * Set a new state for a metric. + * + * Initally metric data is not populated, so `Vue.set` is + * used to add new properties to the metric. + * + * @param {Object} metric - Metric object as defined in the dashboard + * @param {Object} state - New state + * @param {Array|null} state.result - Array of results + * @param {String} state.error - Error code from metricsErrors + * @param {Boolean} state.loading - True if the metric is loading + */ +const setMetricState = (metric, { result = null, error = null, loading = false }) => { + Vue.set(metric, 'result', result); + Vue.set(metric, 'error', error); + Vue.set(metric, 'loading', loading); +}; + +/** + * Maps a backened error state to a `metricsErrors` constant + * @param {Object} error - Error from backend response + */ +const getMetricError = error => { + if (!error) { + return metricsErrors.UNKNOWN_ERROR; + } + + // Special error responses + if (error.message === BACKOFF_TIMEOUT) { + return metricsErrors.TIMEOUT; + } + + // Axios error responses + const { response } = error; + if (response && response.status === httpStatusCodes.SERVICE_UNAVAILABLE) { + return metricsErrors.CONNECTION_FAILED; + } else if (response && response.status === httpStatusCodes.BAD_REQUEST) { + // Note: "error.response.data.error" may contain Prometheus error information + return metricsErrors.BAD_DATA; + } + + return metricsErrors.UNKNOWN_ERROR; +}; + export default { + /** + * Dashboard panels structure and global state + */ [types.REQUEST_METRICS_DATA](state) { state.emptyState = 'loading'; state.showEmptyState = true; @@ -40,6 +110,10 @@ export default { state.emptyState = error ? 'unableToConnect' : 'noData'; state.showEmptyState = true; }, + + /** + * Deployments and environments + */ [types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS](state, deployments) { state.deploymentData = deployments; }, @@ -53,28 +127,46 @@ export default { state.environments = []; }, - [types.SET_QUERY_RESULT](state, { metricId, result }) { - if (!metricId || !result || result.length === 0) { + /** + * Individual panel/metric results + */ + [types.REQUEST_METRIC_RESULT](state, { metricId }) { + const metric = findMetricInDashboard(metricId, state.dashboard); + + setMetricState(metric, { + loading: true, + }); + }, + [types.RECEIVE_METRIC_RESULT_SUCCESS](state, { metricId, result }) { + if (!metricId) { return; } state.showEmptyState = false; - /** - * Search the dashboard state for a matching id - */ - state.dashboard.panel_groups.forEach(group => { - group.panels.forEach(panel => { - panel.metrics.forEach(metric => { - if (metric.metric_id === metricId) { - // ensure dates/numbers are correctly formatted for charts - const normalizedResults = result.map(normalizeQueryResult); - Vue.set(metric, 'result', Object.freeze(normalizedResults)); - } - }); + const metric = findMetricInDashboard(metricId, state.dashboard); + if (!result || result.length === 0) { + // If no data is return we still consider it an error and set it to undefined + setMetricState(metric, { + error: metricsErrors.NO_DATA, + }); + } else { + const normalizedResults = result.map(normalizeQueryResult); + setMetricState(metric, { + result: Object.freeze(normalizedResults), }); + } + }, + [types.RECEIVE_METRIC_RESULT_ERROR](state, { metricId, error }) { + if (!metricId) { + return; + } + const metric = findMetricInDashboard(metricId, state.dashboard); + setMetricState(metric, { + error: getMetricError(error), }); }, + [types.SET_ENDPOINTS](state, endpoints) { state.metricsEndpoint = endpoints.metricsEndpoint; state.environmentsEndpoint = endpoints.environmentsEndpoint; diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js index 88f333aeb80..ee8a85ea222 100644 --- a/app/assets/javascripts/monitoring/stores/state.js +++ b/app/assets/javascripts/monitoring/stores/state.js @@ -8,9 +8,11 @@ export default () => ({ emptyState: 'gettingStarted', showEmptyState: true, showErrorBanner: true, + dashboard: { panel_groups: [], }, + deploymentData: [], environments: [], allDashboards: [], |