diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-24 03:08:44 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-24 03:08:44 +0000 |
| commit | 4870899d6cec693b58acbef91636e1310160fa28 (patch) | |
| tree | 305628a2a8436e77b0c3972f8053df771a89843c /app/assets/javascripts/monitoring/stores | |
| parent | 082b24b03bbb9dca7edf1341aa7b0a51c9aeb18b (diff) | |
| download | gitlab-ce-4870899d6cec693b58acbef91636e1310160fa28.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/monitoring/stores')
4 files changed, 149 insertions, 22 deletions
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 6527a799759..aaa7865f30a 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -21,6 +21,7 @@ import { PROMETHEUS_TIMEOUT, ENVIRONMENT_AVAILABLE_STATE, DEFAULT_DASHBOARD_PATH, + VARIABLE_TYPES, } from '../constants'; function prometheusMetricQueryParams(timeRange) { @@ -191,8 +192,12 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => { return Promise.reject(); } + // Time range params must be pre-calculated once for all metrics and options + // A subsequent call, may calculate a different time range const defaultQueryParams = prometheusMetricQueryParams(state.timeRange); + dispatch('fetchVariableMetricLabelValues', { defaultQueryParams }); + const promises = []; state.dashboard.panelGroups.forEach(group => { group.panels.forEach(panel => { @@ -466,10 +471,41 @@ export const duplicateSystemDashboard = ({ state }, payload) => { // Variables manipulation export const updateVariablesAndFetchData = ({ commit, dispatch }, updatedVariable) => { - commit(types.UPDATE_VARIABLES, updatedVariable); + commit(types.UPDATE_VARIABLE_VALUE, updatedVariable); return dispatch('fetchDashboardData'); }; +export const fetchVariableMetricLabelValues = ({ state, commit }, { defaultQueryParams }) => { + const { start_time, end_time } = defaultQueryParams; + const optionsRequests = []; + + Object.entries(state.variables).forEach(([key, variable]) => { + if (variable.type === VARIABLE_TYPES.metric_label_values) { + const { prometheusEndpointPath, label } = variable.options; + + const optionsRequest = backOffRequest(() => + axios.get(prometheusEndpointPath, { + params: { start_time, end_time }, + }), + ) + .then(({ data }) => data.data) + .then(data => { + commit(types.UPDATE_VARIABLE_METRIC_LABEL_VALUES, { variable, label, data }); + }) + .catch(() => { + createFlash( + sprintf(s__('Metrics|There was an error getting options for variable "%{name}".'), { + name: key, + }), + ); + }); + optionsRequests.push(optionsRequest); + } + }); + + return Promise.all(optionsRequests); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js index 17563f966f5..d43065749c1 100644 --- a/app/assets/javascripts/monitoring/stores/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/mutation_types.js @@ -3,7 +3,8 @@ export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD'; export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS'; export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE'; export const SET_VARIABLES = 'SET_VARIABLES'; -export const UPDATE_VARIABLES = 'UPDATE_VARIABLES'; +export const UPDATE_VARIABLE_VALUE = 'UPDATE_VARIABLE_VALUE'; +export const UPDATE_VARIABLE_METRIC_LABEL_VALUES = 'UPDATE_VARIABLE_METRIC_LABEL_VALUES'; export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING'; export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS'; diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index 08e6f00b866..53c8029e46b 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -2,9 +2,10 @@ import Vue from 'vue'; import { pick } from 'lodash'; import * as types from './mutation_types'; import { mapToDashboardViewModel, normalizeQueryResponseData } from './utils'; +import httpStatusCodes from '~/lib/utils/http_status'; import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils'; import { endpointKeys, initialStateKeys, metricStates } from '../constants'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { optionsFromSeriesData } from './variable_mapping'; /** * Locate and return a metric in the dashboard by its id @@ -205,10 +206,16 @@ export default { [types.SET_VARIABLES](state, variables) { state.variables = variables; }, - [types.UPDATE_VARIABLES](state, updatedVariable) { - Object.assign(state.variables[updatedVariable.key], { - ...state.variables[updatedVariable.key], - value: updatedVariable.value, + [types.UPDATE_VARIABLE_VALUE](state, { key, value }) { + Object.assign(state.variables[key], { + ...state.variables[key], + value, }); }, + [types.UPDATE_VARIABLE_METRIC_LABEL_VALUES](state, { variable, label, data = [] }) { + const values = optionsFromSeriesData({ label, data }); + + // Add new options with assign to ensure Vue reactivity + Object.assign(variable.options, { values }); + }, }; diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js index c0a8150063b..0b268402992 100644 --- a/app/assets/javascripts/monitoring/stores/variable_mapping.js +++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js @@ -46,7 +46,7 @@ const textAdvancedVariableParser = advTextVar => ({ * @param {Object} custom variable option * @returns {Object} normalized custom variable options */ -const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, value }) => ({ +const normalizeVariableValues = ({ default: defaultOpt = false, text, value }) => ({ default: defaultOpt, text: text || value, value, @@ -59,17 +59,19 @@ const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, val * The default value is the option with default set to true or the first option * if none of the options have default prop true. * - * @param {Object} advVariable advance custom variable + * @param {Object} advVariable advanced custom variable * @returns {Object} */ const customAdvancedVariableParser = advVariable => { - const options = (advVariable?.options?.values ?? []).map(normalizeCustomVariableOptions); - const defaultOpt = options.find(opt => opt.default === true) || options[0]; + const values = (advVariable?.options?.values ?? []).map(normalizeVariableValues); + const defaultValue = values.find(opt => opt.default === true) || values[0]; return { type: VARIABLE_TYPES.custom, label: advVariable.label, - value: defaultOpt?.value, - options, + value: defaultValue?.value, + options: { + values, + }, }; }; @@ -80,7 +82,7 @@ const customAdvancedVariableParser = advVariable => { * @param {String} opt option from simple custom variable * @returns {Object} */ -const parseSimpleCustomOptions = opt => ({ text: opt, value: opt }); +export const parseSimpleCustomValues = opt => ({ text: opt, value: opt }); /** * Custom simple variables are rendered as dropdown elements in the dashboard @@ -95,12 +97,28 @@ const parseSimpleCustomOptions = opt => ({ text: opt, value: opt }); * @returns {Object} */ const customSimpleVariableParser = simpleVar => { - const options = (simpleVar || []).map(parseSimpleCustomOptions); + const values = (simpleVar || []).map(parseSimpleCustomValues); return { type: VARIABLE_TYPES.custom, - value: options[0].value, + value: values[0].value, label: null, - options: options.map(normalizeCustomVariableOptions), + options: { + values: values.map(normalizeVariableValues), + }, + }; +}; + +const metricLabelValuesVariableParser = variable => { + const { label, options = {} } = variable; + return { + type: VARIABLE_TYPES.metric_label_values, + value: null, + label, + options: { + prometheusEndpointPath: options.prometheus_endpoint_path || '', + label: options.label || null, + values: [], // values are initially empty + }, }; }; @@ -123,14 +141,16 @@ const isSimpleCustomVariable = customVar => Array.isArray(customVar); * @return {Function} parser method */ const getVariableParser = variable => { - if (isSimpleCustomVariable(variable)) { + if (isString(variable)) { + return textSimpleVariableParser; + } else if (isSimpleCustomVariable(variable)) { return customSimpleVariableParser; - } else if (variable.type === VARIABLE_TYPES.custom) { - return customAdvancedVariableParser; } else if (variable.type === VARIABLE_TYPES.text) { return textAdvancedVariableParser; - } else if (isString(variable)) { - return textSimpleVariableParser; + } else if (variable.type === VARIABLE_TYPES.custom) { + return customAdvancedVariableParser; + } else if (variable.type === VARIABLE_TYPES.metric_label_values) { + return metricLabelValuesVariableParser; } return () => null; }; @@ -200,4 +220,67 @@ export const mergeURLVariables = (varsFromYML = {}) => { return variables; }; +/** + * Converts series data to options that can be added to a + * variable. Series data is returned from the Prometheus API + * `/api/v1/series`. + * + * Finds a `label` in the series data, so it can be used as + * a filter. + * + * For example, for the arguments: + * + * { + * "label": "job" + * "data" : [ + * { + * "__name__" : "up", + * "job" : "prometheus", + * "instance" : "localhost:9090" + * }, + * { + * "__name__" : "up", + * "job" : "node", + * "instance" : "localhost:9091" + * }, + * { + * "__name__" : "process_start_time_seconds", + * "job" : "prometheus", + * "instance" : "localhost:9090" + * } + * ] + * } + * + * It returns all the different "job" values: + * + * [ + * { + * "label": "node", + * "value": "node" + * }, + * { + * "label": "prometheus", + * "value": "prometheus" + * } + * ] + * + * @param {options} options object + * @param {options.seriesLabel} name of the searched series label + * @param {options.data} series data from the series API + * @return {array} Options objects with the shape `{ label, value }` + * + * @see https://prometheus.io/docs/prometheus/latest/querying/api/#finding-series-by-label-matchers + */ +export const optionsFromSeriesData = ({ label, data = [] }) => { + const optionsSet = data.reduce((set, seriesObject) => { + // Use `new Set` to deduplicate options + if (seriesObject[label]) { + set.add(seriesObject[label]); + } + return set; + }, new Set()); + + return [...optionsSet].map(parseSimpleCustomValues); +}; + export default {}; |
