diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
commit | 9f46488805e86b1bc341ea1620b866016c2ce5ed (patch) | |
tree | f9748c7e287041e37d6da49e0a29c9511dc34768 /app/assets/javascripts/monitoring/stores | |
parent | dfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff) | |
download | gitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz |
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'app/assets/javascripts/monitoring/stores')
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 {}; |