summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/monitoring/stores
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/monitoring/stores')
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js84
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js28
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js10
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js36
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js17
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/variable_mapping.js167
7 files changed, 327 insertions, 17 deletions
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index f04f775761c..b057afa2264 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -3,6 +3,8 @@ import * as types from './mutation_types';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
+import { parseTemplatingVariables } from './variable_mapping';
+import { mergeURLVariables } from '../utils';
import {
gqClient,
parseEnvironmentsResponse,
@@ -13,11 +15,7 @@ import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
import getAnnotations from '../queries/getAnnotations.query.graphql';
import statusCodes from '../../lib/utils/http_status';
-import {
- backOff,
- convertObjectPropsToCamelCase,
- isFeatureFlagEnabled,
-} from '../../lib/utils/common_utils';
+import { backOff, convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
import {
@@ -80,6 +78,10 @@ export const setTimeRange = ({ commit }, timeRange) => {
commit(types.SET_TIME_RANGE, timeRange);
};
+export const setVariables = ({ commit }, variables) => {
+ commit(types.SET_VARIABLES, variables);
+};
+
export const filterEnvironments = ({ commit, dispatch }, searchTerm) => {
commit(types.SET_ENVIRONMENTS_FILTER, searchTerm);
dispatch('fetchEnvironmentsData');
@@ -89,19 +91,30 @@ export const setShowErrorBanner = ({ commit }, enabled) => {
commit(types.SET_SHOW_ERROR_BANNER, enabled);
};
+export const setExpandedPanel = ({ commit }, { group, panel }) => {
+ commit(types.SET_EXPANDED_PANEL, { group, panel });
+};
+
+export const clearExpandedPanel = ({ commit }) => {
+ commit(types.SET_EXPANDED_PANEL, {
+ group: null,
+ panel: null,
+ });
+};
+
// All Data
+/**
+ * Fetch all dashboard data.
+ *
+ * @param {Object} store
+ * @returns A promise that resolves when the dashboard
+ * skeleton has been loaded.
+ */
export const fetchData = ({ dispatch }) => {
dispatch('fetchEnvironmentsData');
dispatch('fetchDashboard');
- /**
- * Annotations data is not yet fetched. This will be
- * ready after the BE piece is implemented.
- * https://gitlab.com/gitlab-org/gitlab/-/issues/211330
- */
- if (isFeatureFlagEnabled('metricsDashboardAnnotations')) {
- dispatch('fetchAnnotations');
- }
+ dispatch('fetchAnnotations');
};
// Metrics dashboard
@@ -148,6 +161,7 @@ export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response
commit(types.SET_ALL_DASHBOARDS, all_dashboards);
commit(types.RECEIVE_METRICS_DASHBOARD_SUCCESS, dashboard);
+ commit(types.SET_VARIABLES, mergeURLVariables(parseTemplatingVariables(dashboard.templating)));
commit(types.SET_ENDPOINTS, convertObjectPropsToCamelCase(metrics_data));
return dispatch('fetchDashboardData');
@@ -200,12 +214,19 @@ export const fetchDashboardData = ({ state, dispatch, getters }) => {
*
* @param {metric} metric
*/
-export const fetchPrometheusMetric = ({ commit }, { metric, defaultQueryParams }) => {
+export const fetchPrometheusMetric = (
+ { commit, state, getters },
+ { metric, defaultQueryParams },
+) => {
const queryParams = { ...defaultQueryParams };
if (metric.step) {
queryParams.step = metric.step;
}
+ if (Object.keys(state.promVariables).length > 0) {
+ queryParams.variables = getters.getCustomVariablesArray;
+ }
+
commit(types.REQUEST_METRIC_RESULT, { metricId: metric.metricId });
return getPrometheusMetricResult(metric.prometheusEndpointPath, queryParams)
@@ -327,6 +348,35 @@ export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_AN
// Dashboard manipulation
+export const toggleStarredValue = ({ commit, state, getters }) => {
+ const { selectedDashboard } = getters;
+
+ if (state.isUpdatingStarredValue) {
+ // Prevent repeating requests for the same change
+ return;
+ }
+ if (!selectedDashboard) {
+ return;
+ }
+
+ const method = selectedDashboard.starred ? 'DELETE' : 'POST';
+ const url = selectedDashboard.user_starred_path;
+ const newStarredValue = !selectedDashboard.starred;
+
+ commit(types.REQUEST_DASHBOARD_STARRING);
+
+ axios({
+ url,
+ method,
+ })
+ .then(() => {
+ commit(types.RECEIVE_DASHBOARD_STARRING_SUCCESS, newStarredValue);
+ })
+ .catch(() => {
+ commit(types.RECEIVE_DASHBOARD_STARRING_FAILURE);
+ });
+};
+
/**
* Set a new array of metrics to a panel group
* @param {*} data An object containing
@@ -364,5 +414,11 @@ export const duplicateSystemDashboard = ({ state }, payload) => {
});
};
+// Variables manipulation
+
+export const updateVariableValues = ({ commit }, updatedVariable) => {
+ commit(types.UPDATE_VARIABLE_VALUES, updatedVariable);
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js
index a6d80c5063e..ae3ff5596e1 100644
--- a/app/assets/javascripts/monitoring/stores/getters.js
+++ b/app/assets/javascripts/monitoring/stores/getters.js
@@ -1,9 +1,25 @@
+import { flatMap } from 'lodash';
import { NOT_IN_DB_PREFIX } from '../constants';
const metricsIdsInPanel = panel =>
panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
/**
+ * Returns a reference to the currently selected dashboard
+ * from the list of dashboards.
+ *
+ * @param {Object} state
+ */
+export const selectedDashboard = state => {
+ const { allDashboards } = state;
+ return (
+ allDashboards.find(d => d.path === state.currentDashboard) ||
+ allDashboards.find(d => d.default) ||
+ null
+ );
+};
+
+/**
* Get all state for metric in the dashboard or a group. The
* states are not repeated so the dashboard or group can show
* a global state.
@@ -96,5 +112,17 @@ export const filteredEnvironments = state =>
env.name.toLowerCase().includes((state.environmentsSearchTerm || '').trim().toLowerCase()),
);
+/**
+ * Maps an variables object to an array along with stripping
+ * the variable prefix.
+ *
+ * @param {Object} variables - Custom variables provided by the user
+ * @returns {Array} The custom variables array to be send to the API
+ * in the format of [variable1, variable1_value]
+ */
+
+export const getCustomVariablesArray = state =>
+ flatMap(state.promVariables, (variable, key) => [key, variable.value]);
+
// 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 27a9a67edaa..d60334609fd 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -1,7 +1,13 @@
-// Dashboard "skeleton", groups, panels and metrics
+// Dashboard "skeleton", groups, panels, metrics, query variables
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_VARIABLE_VALUES = 'UPDATE_VARIABLE_VALUES';
+
+export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING';
+export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS';
+export const RECEIVE_DASHBOARD_STARRING_FAILURE = 'RECEIVE_DASHBOARD_STARRING_FAILURE';
// Annotations
export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
@@ -31,5 +37,5 @@ export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE'
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
export const SET_PANEL_GROUP_METRICS = 'SET_PANEL_GROUP_METRICS';
-
export const SET_ENVIRONMENTS_FILTER = 'SET_ENVIRONMENTS_FILTER';
+export const SET_EXPANDED_PANEL = 'SET_EXPANDED_PANEL';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index aa31b6642d7..f41cf3fc477 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -1,5 +1,7 @@
-import pick from 'lodash/pick';
+import Vue from 'vue';
+import { pick } from 'lodash';
import * as types from './mutation_types';
+import { selectedDashboard } from './getters';
import { mapToDashboardViewModel, normalizeQueryResult } from './utils';
import { BACKOFF_TIMEOUT } from '../../lib/utils/common_utils';
import { endpointKeys, initialStateKeys, metricStates } from '../constants';
@@ -71,6 +73,23 @@ export default {
state.showEmptyState = true;
},
+ [types.REQUEST_DASHBOARD_STARRING](state) {
+ state.isUpdatingStarredValue = true;
+ },
+ [types.RECEIVE_DASHBOARD_STARRING_SUCCESS](state, newStarredValue) {
+ const dashboard = selectedDashboard(state);
+ const index = state.allDashboards.findIndex(d => d === dashboard);
+
+ state.isUpdatingStarredValue = false;
+
+ // Trigger state updates in the reactivity system for this change
+ // https://vuejs.org/v2/guide/reactivity.html#For-Arrays
+ Vue.set(state.allDashboards, index, { ...dashboard, starred: newStarredValue });
+ },
+ [types.RECEIVE_DASHBOARD_STARRING_FAILURE](state) {
+ state.isUpdatingStarredValue = false;
+ },
+
/**
* Deployments and environments
*/
@@ -134,6 +153,8 @@ export default {
metric.loading = false;
metric.result = null;
},
+
+ // Parameters and other information
[types.SET_INITIAL_STATE](state, initialState = {}) {
Object.assign(state, pick(initialState, initialStateKeys));
},
@@ -163,4 +184,17 @@ export default {
[types.SET_ENVIRONMENTS_FILTER](state, searchTerm) {
state.environmentsSearchTerm = searchTerm;
},
+ [types.SET_EXPANDED_PANEL](state, { group, panel }) {
+ state.expandedPanel.group = group;
+ state.expandedPanel.panel = panel;
+ },
+ [types.SET_VARIABLES](state, variables) {
+ state.promVariables = variables;
+ },
+ [types.UPDATE_VARIABLE_VALUES](state, updatedVariable) {
+ Object.assign(state.promVariables[updatedVariable.key], {
+ ...state.promVariables[updatedVariable.key],
+ value: updatedVariable.value,
+ });
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index e60510e747b..9ae1da93e5f 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -14,10 +14,27 @@ export default () => ({
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
+ isUpdatingStarredValue: false,
dashboard: {
panelGroups: [],
},
+ /**
+ * Panel that is currently "zoomed" in as
+ * a single panel in view.
+ */
+ expandedPanel: {
+ /**
+ * {?String} Panel's group name.
+ */
+ group: null,
+ /**
+ * {?Object} Panel content from `dashboard`
+ * null when no panel is expanded.
+ */
+ panel: null,
+ },
allDashboards: [],
+ promVariables: {},
// Other project data
annotations: [],
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 9f06d18c46f..a47e5f598f5 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -144,6 +144,7 @@ const mapYAxisToViewModel = ({
* @returns {Object}
*/
const mapPanelToViewModel = ({
+ id = null,
title = '',
type,
x_axis = {},
@@ -162,6 +163,7 @@ const mapPanelToViewModel = ({
const yAxis = mapYAxisToViewModel({ name: y_label, ...y_axis }); // eslint-disable-line babel/camelcase
return {
+ id,
title,
type,
xLabel: xAxis.name,
diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js
new file mode 100644
index 00000000000..bfb469da19e
--- /dev/null
+++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js
@@ -0,0 +1,167 @@
+import { isString } from 'lodash';
+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 normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, value }) => ({
+ default: defaultOpt,
+ text,
+ 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 advance custom variable
+ * @returns {Object}
+ */
+const customAdvancedVariableParser = advVariable => {
+ const options = (advVariable?.options?.values ?? []).map(normalizeCustomVariableOptions);
+ const defaultOpt = options.find(opt => opt.default === true) || options[0];
+ return {
+ type: VARIABLE_TYPES.custom,
+ label: advVariable.label,
+ value: defaultOpt?.value,
+ options,
+ };
+};
+
+/**
+ * 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}
+ */
+const parseSimpleCustomOptions = 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 options = (simpleVar || []).map(parseSimpleCustomOptions);
+ return {
+ type: VARIABLE_TYPES.custom,
+ value: options[0].value,
+ label: null,
+ options: options.map(normalizeCustomVariableOptions),
+ };
+};
+
+/**
+ * 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 (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;
+ }
+ 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.
+ *
+ * This method currently is not used anywhere. Once the issue
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/214536 is completed,
+ * this method will have been used by the monitoring dashboard.
+ *
+ * @param {Object} templating templating variables from the dashboard yml file
+ * @returns {Object} a map of processed templating variables
+ */
+export const parseTemplatingVariables = ({ variables = {} } = {}) =>
+ Object.entries(variables).reduce((acc, [key, variable]) => {
+ // get the parser
+ const parser = getVariableParser(variable);
+ // parse the variable
+ const parsedVar = parser(variable);
+ // for simple custom variable label is null and it should be
+ // replace with key instead
+ if (parsedVar) {
+ acc[key] = {
+ ...parsedVar,
+ label: parsedVar.label || key,
+ };
+ }
+ return acc;
+ }, {});
+
+export default {};