diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
commit | 8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch) | |
tree | a77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/assets/javascripts/monitoring/stores | |
parent | 00b35af3db1abfe813a778f643dad221aad51fca (diff) | |
download | gitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz |
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/assets/javascripts/monitoring/stores')
8 files changed, 172 insertions, 35 deletions
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 9e3edfb495d..3a9cccec438 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -3,8 +3,6 @@ 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, @@ -161,7 +159,6 @@ 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'); @@ -223,7 +220,7 @@ export const fetchPrometheusMetric = ( queryParams.step = metric.step; } - if (Object.keys(state.promVariables).length > 0) { + if (Object.keys(state.variables).length > 0) { queryParams = { ...queryParams, ...getters.getCustomVariablesParams, @@ -317,8 +314,7 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => { export const fetchAnnotations = ({ state, dispatch }) => { const { start } = convertToFixedRange(state.timeRange); - const dashboardPath = - state.currentDashboard === '' ? DEFAULT_DASHBOARD_PATH : state.currentDashboard; + const dashboardPath = state.currentDashboard || DEFAULT_DASHBOARD_PATH; return gqClient .mutate({ mutation: getAnnotations, @@ -373,7 +369,7 @@ export const toggleStarredValue = ({ commit, state, getters }) => { method, }) .then(() => { - commit(types.RECEIVE_DASHBOARD_STARRING_SUCCESS, newStarredValue); + commit(types.RECEIVE_DASHBOARD_STARRING_SUCCESS, { selectedDashboard, newStarredValue }); }) .catch(() => { commit(types.RECEIVE_DASHBOARD_STARRING_FAILURE); @@ -419,8 +415,10 @@ export const duplicateSystemDashboard = ({ state }, payload) => { // Variables manipulation -export const updateVariableValues = ({ commit }, updatedVariable) => { - commit(types.UPDATE_VARIABLE_VALUES, updatedVariable); +export const updateVariablesAndFetchData = ({ commit, dispatch }, updatedVariable) => { + commit(types.UPDATE_VARIABLES, updatedVariable); + + return dispatch('fetchDashboardData'); }; // prevent babel-plugin-rewire from generating an invalid default during karma tests diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js index f309addee6b..b7681012472 100644 --- a/app/assets/javascripts/monitoring/stores/getters.js +++ b/app/assets/javascripts/monitoring/stores/getters.js @@ -1,5 +1,5 @@ import { NOT_IN_DB_PREFIX } from '../constants'; -import { addPrefixToCustomVariableParams } from './utils'; +import { addPrefixToCustomVariableParams, addDashboardMetaDataToLink } from './utils'; const metricsIdsInPanel = panel => panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId); @@ -113,6 +113,22 @@ export const filteredEnvironments = state => ); /** + * User-defined links from the yml file can have other + * dashboard-related metadata baked into it. This method + * returns modified links which will get rendered in the + * metrics dashboard + * + * @param {Object} state + * @returns {Array} modified array of links + */ +export const linksWithMetadata = state => { + const metadata = { + timeRange: state.timeRange, + }; + return state.links?.map(addDashboardMetaDataToLink(metadata)); +}; + +/** * Maps an variables object to an array along with stripping * the variable prefix. * @@ -133,8 +149,8 @@ export const filteredEnvironments = state => */ export const getCustomVariablesParams = state => - Object.keys(state.promVariables).reduce((acc, variable) => { - acc[addPrefixToCustomVariableParams(variable)] = state.promVariables[variable]?.value; + Object.keys(state.variables).reduce((acc, variable) => { + acc[addPrefixToCustomVariableParams(variable)] = state.variables[variable]?.value; return acc; }, {}); diff --git a/app/assets/javascripts/monitoring/stores/index.js b/app/assets/javascripts/monitoring/stores/index.js index f08a6402aa6..213a8508aa2 100644 --- a/app/assets/javascripts/monitoring/stores/index.js +++ b/app/assets/javascripts/monitoring/stores/index.js @@ -15,11 +15,15 @@ export const monitoringDashboard = { state, }; -export const createStore = () => +export const createStore = (initState = {}) => new Vuex.Store({ modules: { - monitoringDashboard, + monitoringDashboard: { + ...monitoringDashboard, + state: { + ...state(), + ...initState, + }, + }, }, }); - -export default createStore(); diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js index d60334609fd..4593461776b 100644 --- a/app/assets/javascripts/monitoring/stores/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/mutation_types.js @@ -3,7 +3,7 @@ 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 UPDATE_VARIABLES = 'UPDATE_VARIABLES'; export const REQUEST_DASHBOARD_STARRING = 'REQUEST_DASHBOARD_STARRING'; export const RECEIVE_DASHBOARD_STARRING_SUCCESS = 'RECEIVE_DASHBOARD_STARRING_SUCCESS'; diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index f41cf3fc477..2d63fdd6e34 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -1,7 +1,6 @@ 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'; @@ -61,8 +60,14 @@ export default { state.emptyState = 'loading'; state.showEmptyState = true; }, - [types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboard) { - state.dashboard = mapToDashboardViewModel(dashboard); + [types.RECEIVE_METRICS_DASHBOARD_SUCCESS](state, dashboardYML) { + const { dashboard, panelGroups, variables, links } = mapToDashboardViewModel(dashboardYML); + state.dashboard = { + dashboard, + panelGroups, + }; + state.variables = variables; + state.links = links; if (!state.dashboard.panelGroups.length) { state.emptyState = 'noData'; @@ -76,15 +81,14 @@ export default { [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); + [types.RECEIVE_DASHBOARD_STARRING_SUCCESS](state, { selectedDashboard, newStarredValue }) { + const index = state.allDashboards.findIndex(d => d === selectedDashboard); 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 }); + Vue.set(state.allDashboards, index, { ...selectedDashboard, starred: newStarredValue }); }, [types.RECEIVE_DASHBOARD_STARRING_FAILURE](state) { state.isUpdatingStarredValue = false; @@ -189,11 +193,11 @@ export default { state.expandedPanel.panel = panel; }, [types.SET_VARIABLES](state, variables) { - state.promVariables = variables; + state.variables = variables; }, - [types.UPDATE_VARIABLE_VALUES](state, updatedVariable) { - Object.assign(state.promVariables[updatedVariable.key], { - ...state.promVariables[updatedVariable.key], + [types.UPDATE_VARIABLES](state, updatedVariable) { + Object.assign(state.variables[updatedVariable.key], { + ...state.variables[updatedVariable.key], value: updatedVariable.value, }); }, diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js index 9ae1da93e5f..8000f27c0d5 100644 --- a/app/assets/javascripts/monitoring/stores/state.js +++ b/app/assets/javascripts/monitoring/stores/state.js @@ -1,10 +1,11 @@ import invalidUrl from '~/lib/utils/invalid_url'; +import { timezones } from '../format_date'; export default () => ({ // API endpoints - metricsEndpoint: null, deploymentsEndpoint: null, dashboardEndpoint: invalidUrl, + dashboardsEndpoint: invalidUrl, // Dashboard request parameters timeRange: null, @@ -34,14 +35,24 @@ export default () => ({ panel: null, }, allDashboards: [], - promVariables: {}, - + /** + * User-defined custom variables are passed + * via the dashboard yml file. + */ + variables: {}, + /** + * User-defined custom links are passed + * via the dashboard yml file. + */ + links: [], // Other project data + dashboardTimezone: timezones.LOCAL, annotations: [], deploymentData: [], environments: [], environmentsSearchTerm: '', environmentsLoading: false, + currentEnvironmentName: null, // GitLab paths to other pages projectPath: null, diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index b6817e7279a..058fab5f4fc 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -2,7 +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 } from '../constants'; +import { parseTemplatingVariables } from './variable_mapping'; +import { NOT_IN_DB_PREFIX, linkTypes } from '../constants'; +import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants'; +import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range'; +import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility'; export const gqClient = createGqClient( {}, @@ -138,6 +142,24 @@ const mapYAxisToViewModel = ({ }; /** + * Maps a link to its view model, expects an url and + * (optionally) a title. + * + * Unsafe URLs are ignored. + * + * @param {Object} Link + * @returns {Object} Link object with a `title`, `url` and `type` + * + */ +const mapLinksToViewModel = ({ url = null, title = '', type } = {}) => { + return { + title: title || String(url), + type, + url: url && isSafeURL(url) ? String(url) : '#', + }; +}; + +/** * Maps a metrics panel to its view model * * @param {Object} panel - Metrics panel @@ -152,6 +174,7 @@ const mapPanelToViewModel = ({ y_label, y_axis = {}, metrics = [], + links = [], max_value, }) => { // Both `x_axis.name` and `x_label` are supported for now @@ -171,7 +194,8 @@ const mapPanelToViewModel = ({ yAxis, xAxis, maxValue: max_value, - metrics: mapToMetricsViewModel(metrics, yAxis.name), + links: links.map(mapLinksToViewModel), + metrics: mapToMetricsViewModel(metrics), }; }; @@ -190,6 +214,66 @@ const mapToPanelGroupViewModel = ({ group = '', panels = [] }, i) => { }; /** + * Convert dashboard time range to Grafana + * dashboards time range. + * + * @param {Object} timeRange + * @returns {Object} + */ +export const convertToGrafanaTimeRange = timeRange => { + const timeRangeType = getRangeType(timeRange); + if (timeRangeType === DATETIME_RANGE_TYPES.fixed) { + return { + from: new Date(timeRange.start).getTime(), + to: new Date(timeRange.end).getTime(), + }; + } else if (timeRangeType === DATETIME_RANGE_TYPES.rolling) { + const { seconds } = timeRange.duration; + return { + from: `now-${seconds}s`, + to: 'now', + }; + } + // fallback to returning the time range as is + return timeRange; +}; + +/** + * Convert dashboard time ranges to other supported + * link formats. + * + * @param {Object} timeRange metrics dashboard time range + * @param {String} type type of link + * @returns {String} + */ +export const convertTimeRanges = (timeRange, type) => { + if (type === linkTypes.GRAFANA) { + return convertToGrafanaTimeRange(timeRange); + } + return timeRangeToParams(timeRange); +}; + +/** + * Adds dashboard-related metadata to the user-defined links. + * + * As of %13.1, metadata only includes timeRange but in the + * future more info will be added to the links. + * + * @param {Object} metadata + * @returns {Function} + */ +export const addDashboardMetaDataToLink = metadata => link => { + let modifiedLink = { ...link }; + if (metadata.timeRange) { + modifiedLink = { + ...modifiedLink, + url: mergeUrlParams(convertTimeRanges(metadata.timeRange, link.type), link.url), + }; + } + return modifiedLink; +}; + +/** * Maps a dashboard json object to its view model * * @param {Object} dashboard - Dashboard object @@ -197,13 +281,33 @@ const mapToPanelGroupViewModel = ({ group = '', panels = [] }, i) => { * @param {Array} dashboard.panel_groups - Panel groups array * @returns {Object} */ -export const mapToDashboardViewModel = ({ dashboard = '', panel_groups = [] }) => { +export const mapToDashboardViewModel = ({ + dashboard = '', + templating = {}, + links = [], + panel_groups = [], +}) => { return { dashboard, + variables: parseTemplatingVariables(templating), + links: links.map(mapLinksToViewModel), panelGroups: panel_groups.map(mapToPanelGroupViewModel), }; }; +/** + * Processes a single Range vector, part of the result + * of type `matrix` in the form: + * + * { + * "metric": { "<label_name>": "<label_value>", ... }, + * "values": [ [ <unix_time>, "<sample_value>" ], ... ] + * }, + * + * See https://prometheus.io/docs/prometheus/latest/querying/api/#range-vectors + * + * @param {*} timeSeries + */ export const normalizeQueryResult = timeSeries => { let normalizedResult = {}; diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js index bfb469da19e..66b9899f673 100644 --- a/app/assets/javascripts/monitoring/stores/variable_mapping.js +++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js @@ -47,7 +47,7 @@ const textAdvancedVariableParser = advTextVar => ({ */ const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, value }) => ({ default: defaultOpt, - text, + text: text || value, value, }); |