summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/monitoring/stores/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/monitoring/stores/utils.js')
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js198
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;
+};