gitlab-org--gitlab-foss/app/assets/javascripts/monitoring/stores/variable_mapping.js

274 lines
7.9 KiB
JavaScript

import { isString } from 'lodash';
import { templatingVariablesFromUrl } from '../utils';
import { VARIABLE_TYPES } from '../constants';
/**
* This file exclusively deals with parsing user-defined variables
* in dashboard yml file.
*
* As of 13.0, simple text, advanced text, simple custom and
* advanced custom variables are supported.
*
* In the future iterations, text and query variables will be
* supported
*
*/
/**
* Simple text variable is a string value only.
* This method parses such variables to a standard format.
*
* @param {String|Object} simpleTextVar
* @returns {Object}
*/
const textSimpleVariableParser = (simpleTextVar) => ({
type: VARIABLE_TYPES.text,
label: null,
value: simpleTextVar,
});
/**
* Advanced text variable is an object.
* This method parses such variables to a standard format.
*
* @param {Object} advTextVar
* @returns {Object}
*/
const textAdvancedVariableParser = (advTextVar) => ({
type: VARIABLE_TYPES.text,
label: advTextVar.label,
value: advTextVar.options.default_value,
});
/**
* Normalize simple and advanced custom variable options to a standard
* format
* @param {Object} custom variable option
* @returns {Object} normalized custom variable options
*/
const normalizeVariableValues = ({ default: defaultOpt = false, text, value = null }) => ({
default: defaultOpt,
text: text || value,
value,
});
/**
* Custom advanced variables are rendered as dropdown elements in the dashboard
* header. This method parses advanced custom variables.
*
* 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 advanced custom variable
* @returns {Object}
*/
const customAdvancedVariableParser = (advVariable) => {
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,
options: {
values,
},
value: defaultValue?.value || null,
};
};
/**
* Simple custom variables have an array of values.
* This method parses such variables options to a standard format.
*
* @param {String} opt option from simple custom variable
* @returns {Object}
*/
export const parseSimpleCustomValues = (opt) => ({ text: opt, value: opt });
/**
* Custom simple variables are rendered as dropdown elements in the dashboard
* header. This method parses simple custom variables.
*
* Simple custom variables do not have labels so its set to null here.
*
* The default value is set to the first option as the user cannot
* set a default value for this format
*
* @param {Array} customVariable array of options
* @returns {Object}
*/
const customSimpleVariableParser = (simpleVar) => {
const values = (simpleVar || []).map(parseSimpleCustomValues);
return {
type: VARIABLE_TYPES.custom,
label: null,
value: values[0].value || null,
options: {
values: values.map(normalizeVariableValues),
},
};
};
const metricLabelValuesVariableParser = ({ label, options = {} }) => ({
type: VARIABLE_TYPES.metric_label_values,
label,
value: null,
options: {
prometheusEndpointPath: options.prometheus_endpoint_path || '',
label: options.label || null,
values: [], // values are initially empty
},
});
/**
* Utility method to determine if a custom variable is
* simple or not. If its not simple, it is advanced.
*
* @param {Array|Object} customVar Array if simple, object if advanced
* @returns {Boolean} true if simple, false if advanced
*/
const isSimpleCustomVariable = (customVar) => Array.isArray(customVar);
/**
* This method returns a parser based on the type of the variable.
* Currently, the supported variables are simple custom and
* advanced custom only. In the future, this method will support
* text and query variables.
*
* @param {Array|Object} variable
* @return {Function} parser method
*/
const getVariableParser = (variable) => {
if (isString(variable)) {
return textSimpleVariableParser;
} else if (isSimpleCustomVariable(variable)) {
return customSimpleVariableParser;
} else if (variable.type === VARIABLE_TYPES.text) {
return textAdvancedVariableParser;
} else if (variable.type === VARIABLE_TYPES.custom) {
return customAdvancedVariableParser;
} else if (variable.type === VARIABLE_TYPES.metric_label_values) {
return metricLabelValuesVariableParser;
}
return () => null;
};
/**
* This method parses the templating property in the dashboard yml file.
* The templating property has variables that are rendered as input elements
* for the user to edit. The values from input elements are relayed to
* backend and eventually Prometheus API.
*
* @param {Object} templating variables from the dashboard yml file
* @returns {array} An array of variables to display as inputs
*/
export const parseTemplatingVariables = (ymlVariables = {}) =>
Object.entries(ymlVariables).reduce((acc, [name, ymlVariable]) => {
// get the parser
const parser = getVariableParser(ymlVariable);
// parse the variable
const variable = parser(ymlVariable);
// for simple custom variable label is null and it should be
// replace with key instead
if (variable) {
acc.push({
...variable,
name,
label: variable.label || name,
});
}
return acc;
}, []);
/**
* Custom variables are defined in the dashboard yml file
* and their values can be passed through the URL.
*
* On component load, this method merges variables data
* from the yml file with URL data to store in the Vuex store.
* Not all params coming from the URL need to be stored. Only
* the ones that have a corresponding variable defined in the
* yml file.
*
* This ensures that there is always a single source of truth
* for variables
*
* This method can be improved further. See the below issue
* https://gitlab.com/gitlab-org/gitlab/-/issues/217713
*
* @param {array} parsedYmlVariables - template variables from yml file
* @returns {Object}
*/
export const mergeURLVariables = (parsedYmlVariables = []) => {
const varsFromURL = templatingVariablesFromUrl();
parsedYmlVariables.forEach((variable) => {
const { name } = variable;
if (Object.prototype.hasOwnProperty.call(varsFromURL, name)) {
Object.assign(variable, { value: varsFromURL[name] });
}
});
return parsedYmlVariables;
};
/**
* 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);
};