diff options
Diffstat (limited to 'app/assets/javascripts/monitoring/stores/utils.js')
-rw-r--r-- | app/assets/javascripts/monitoring/stores/utils.js | 198 |
1 files changed, 168 insertions, 30 deletions
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index 5795e756282..51562593ee8 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -2,11 +2,11 @@ import { slugify } from '~/lib/utils/text_utility'; import createGqClient, { fetchPolicies } from '~/lib/graphql'; import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { NOT_IN_DB_PREFIX, linkTypes } from '../constants'; import { mergeURLVariables, parseTemplatingVariables } from './variable_mapping'; import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants'; import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range'; import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility'; +import { NOT_IN_DB_PREFIX, linkTypes, OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX } from '../constants'; export const gqClient = createGqClient( {}, @@ -165,7 +165,7 @@ const mapLinksToViewModel = ({ url = null, title = '', type } = {}) => { * @param {Object} panel - Metrics panel * @returns {Object} */ -const mapPanelToViewModel = ({ +export const mapPanelToViewModel = ({ id = null, title = '', type, @@ -173,6 +173,7 @@ const mapPanelToViewModel = ({ x_label, y_label, y_axis = {}, + field, metrics = [], links = [], max_value, @@ -193,6 +194,7 @@ const mapPanelToViewModel = ({ y_label: yAxis.name, // Changing y_label to yLabel is pending https://gitlab.com/gitlab-org/gitlab/issues/207198 yAxis, xAxis, + field, maxValue: max_value, links: links.map(mapLinksToViewModel), metrics: mapToMetricsViewModel(metrics), @@ -289,49 +291,157 @@ export const mapToDashboardViewModel = ({ }) => { return { dashboard, - variables: mergeURLVariables(parseTemplatingVariables(templating)), + variables: mergeURLVariables(parseTemplatingVariables(templating.variables)), links: links.map(mapLinksToViewModel), panelGroups: panel_groups.map(mapToPanelGroupViewModel), }; }; +// Prometheus Results Parsing + +const dateTimeFromUnixTime = unixTime => new Date(unixTime * 1000).toISOString(); + +const mapScalarValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), Number(value)]; + +// Note: `string` value type is unused as of prometheus 2.19. +const mapStringValue = ([unixTime, value]) => [dateTimeFromUnixTime(unixTime), value]; + +/** + * Processes a scalar result. + * + * The corresponding result property has the following format: + * + * [ <unix_time>, "<scalar_value>" ] + * + * @param {array} result + * @returns {array} + */ +const normalizeScalarResult = result => [ + { + metric: {}, + value: mapScalarValue(result), + values: [mapScalarValue(result)], + }, +]; + +/** + * Processes a string result. + * + * The corresponding result property has the following format: + * + * [ <unix_time>, "<string_value>" ] + * + * Note: This value type is unused as of prometheus 2.19. + * + * @param {array} result + * @returns {array} + */ +const normalizeStringResult = result => [ + { + metric: {}, + value: mapStringValue(result), + values: [mapStringValue(result)], + }, +]; + +/** + * Proccesses an instant vector. + * + * Instant vectors are returned as result type `vector`. + * + * The corresponding result property has the following format: + * + * [ + * { + * "metric": { "<label_name>": "<label_value>", ... }, + * "value": [ <unix_time>, "<sample_value>" ], + * "values": [ [ <unix_time>, "<sample_value>" ] ] + * }, + * ... + * ] + * + * `metric` - Key-value pairs object representing metric measured + * `value` - The vector result + * `values` - An array with a single value representing the result + * + * This method also adds the matrix version of the vector + * by introducing a `values` array with a single element. This + * allows charts to default to `values` if needed. + * + * @param {array} result + * @returns {array} + */ +const normalizeVectorResult = result => + result.map(({ metric, value }) => { + const scalar = mapScalarValue(value); + // Add a single element to `values`, to support matrix + // style charts. + return { metric, value: scalar, values: [scalar] }; + }); + /** - * Processes a single Range vector, part of the result - * of type `matrix` in the form: + * Range vectors are returned as result type matrix. + * + * The corresponding result property has the following format: * * { * "metric": { "<label_name>": "<label_value>", ... }, + * "value": [ <unix_time>, "<sample_value>" ], * "values": [ [ <unix_time>, "<sample_value>" ], ... ] * }, * + * `metric` - Key-value pairs object representing metric measured + * `value` - The last (more recent) result + * `values` - A range of results for the metric + * * See https://prometheus.io/docs/prometheus/latest/querying/api/#range-vectors * - * @param {*} timeSeries + * @param {array} result + * @returns {object} Normalized result. */ -export const normalizeQueryResult = timeSeries => { - let normalizedResult = {}; - - if (timeSeries.values) { - normalizedResult = { - ...timeSeries, - values: timeSeries.values.map(([timestamp, value]) => [ - new Date(timestamp * 1000).toISOString(), - Number(value), - ]), - }; - // Check result for empty data - normalizedResult.values = normalizedResult.values.filter(series => { - const hasValue = d => !Number.isNaN(d[1]) && (d[1] !== null || d[1] !== undefined); - return series.find(hasValue); - }); - } else if (timeSeries.value) { - normalizedResult = { - ...timeSeries, - value: [new Date(timeSeries.value[0] * 1000).toISOString(), Number(timeSeries.value[1])], +const normalizeResultMatrix = result => + result.map(({ metric, values }) => { + const mappedValues = values.map(mapScalarValue); + return { + metric, + value: mappedValues[mappedValues.length - 1], + values: mappedValues, }; - } + }); - return normalizedResult; +/** + * Parse response data from a Prometheus Query that comes + * in the format: + * + * { + * "resultType": "matrix" | "vector" | "scalar" | "string", + * "result": <value> + * } + * + * @see https://prometheus.io/docs/prometheus/latest/querying/api/#expression-query-result-formats + * + * @param {object} data - Data containing results and result type. + * @returns {object} - A result array of metric results: + * [ + * { + * metric: { ... }, + * value: ['2015-07-01T20:10:51.781Z', '1'], + * values: [['2015-07-01T20:10:51.781Z', '1'] , ... ], + * }, + * ... + * ] + * + */ +export const normalizeQueryResponseData = data => { + const { resultType, result } = data; + if (resultType === 'vector') { + return normalizeVectorResult(result); + } else if (resultType === 'scalar') { + return normalizeScalarResult(result); + } else if (resultType === 'string') { + return normalizeStringResult(result); + } + return normalizeResultMatrix(result); }; /** @@ -345,7 +455,35 @@ export const normalizeQueryResult = timeSeries => { * * This is currently only used by getters/getCustomVariablesParams * - * @param {String} key Variable key that needs to be prefixed + * @param {String} name Variable key that needs to be prefixed * @returns {String} */ -export const addPrefixToCustomVariableParams = key => `variables[${key}]`; +export const addPrefixToCustomVariableParams = name => `variables[${name}]`; + +/** + * Normalize custom dashboard paths. This method helps support + * metrics dashboard to work with custom dashboard file names instead + * of the entire path. + * + * If dashboard is empty, it is the default dashboard. + * If dashboard is set, it usually is a custom dashboard unless + * explicitly it is set to default dashboard path. + * + * @param {String} dashboard dashboard path + * @param {String} dashboardPrefix custom dashboard directory prefix + * @returns {String} normalized dashboard path + */ +export const normalizeCustomDashboardPath = (dashboard, dashboardPrefix = '') => { + const currDashboard = dashboard || ''; + let dashboardPath = `${dashboardPrefix}/${currDashboard}`; + + if (!currDashboard) { + dashboardPath = ''; + } else if ( + currDashboard.startsWith(dashboardPrefix) || + currDashboard.startsWith(OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX) + ) { + dashboardPath = currDashboard; + } + return dashboardPath; +}; |