diff options
Diffstat (limited to 'app/assets/javascripts/monitoring/utils.js')
-rw-r--r-- | app/assets/javascripts/monitoring/utils.js | 201 |
1 files changed, 199 insertions, 2 deletions
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 7c6cd19eb7b..1f028ffbcad 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -1,9 +1,23 @@ -import { queryToObject, mergeUrlParams, removeParams } from '~/lib/utils/url_utility'; +import { pickBy, mapKeys } from 'lodash'; +import { + queryToObject, + mergeUrlParams, + removeParams, + updateHistory, +} from '~/lib/utils/url_utility'; import { timeRangeParamNames, timeRangeFromParams, timeRangeToParams, } from '~/lib/utils/datetime_range'; +import { VARIABLE_PREFIX } from './constants'; + +/** + * List of non time range url parameters + * This will be removed once we add support for free text variables + * via the dashboard yaml files in https://gitlab.com/gitlab-org/gitlab/-/issues/215689 + */ +export const dashboardParams = ['dashboard', 'group', 'title', 'y_label', 'embedded']; /** * This method is used to validate if the graph data format for a chart component @@ -28,7 +42,6 @@ export const graphDataValidatorForValues = (isValues, graphData) => { ); }; -/* eslint-disable @gitlab/require-i18n-strings */ /** * Checks that element that triggered event is located on cluster health check dashboard * @param {HTMLElement} element to check against @@ -36,6 +49,7 @@ export const graphDataValidatorForValues = (isValues, graphData) => { */ const isClusterHealthBoard = () => (document.body.dataset.page || '').includes(':clusters:show'); +/* eslint-disable @gitlab/require-i18n-strings */ /** * Tracks snowplow event when user generates link to metric chart * @param {String} chart link that will be sent as a property for the event @@ -71,6 +85,7 @@ export const downloadCSVOptions = title => { return { category, action, label: 'Chart title', property: title }; }; +/* eslint-enable @gitlab/require-i18n-strings */ /** * Generate options for snowplow to track adding a new metric via the dashboard @@ -113,6 +128,78 @@ export const timeRangeFromUrl = (search = window.location.search) => { }; /** + * Variable labels are used as names for the dropdowns and also + * as URL params. Prefixing the name reduces the risk of + * collision with other URL params + * + * @param {String} label label for the template variable + * @returns {String} + */ +export const addPrefixToLabel = label => `${VARIABLE_PREFIX}${label}`; + +/** + * Before the templating variables are passed to the backend the + * prefix needs to be removed. + * + * This method removes the prefix at the beginning of the string. + * + * @param {String} label label to remove prefix from + * @returns {String} + */ +export const removePrefixFromLabel = label => + (label || '').replace(new RegExp(`^${VARIABLE_PREFIX}`), ''); + +/** + * Convert parsed template variables to an object + * with just keys and values. Prepare the promVariables + * to be added to the URL. Keys of the object will + * have a prefix so that these params can be + * differentiated from other URL params. + * + * @param {Object} variables + * @returns {Object} + */ +export const convertVariablesForURL = variables => + Object.keys(variables || {}).reduce((acc, key) => { + acc[addPrefixToLabel(key)] = variables[key]?.value; + return acc; + }, {}); + +/** + * User-defined variables from the URL are extracted. The variables + * begin with a constant prefix so that it doesn't collide with + * other URL params. + * + * @param {String} New URL + * @returns {Object} The custom variables defined by the user in the URL + */ + +export const getPromCustomVariablesFromUrl = (search = window.location.search) => { + const params = queryToObject(search); + // pick the params with variable prefix + const paramsWithVars = pickBy(params, (val, key) => key.startsWith(VARIABLE_PREFIX)); + // remove the prefix before storing in the Vuex store + return mapKeys(paramsWithVars, (val, key) => removePrefixFromLabel(key)); +}; + +/** + * Update the URL with promVariables. This usually get triggered when + * the user interacts with the dynamic input elements in the monitoring + * dashboard header. + * + * @param {Object} promVariables user defined variables + */ +export const setPromCustomVariablesFromUrl = promVariables => { + // prep the variables to append to URL + const parsedVariables = convertVariablesForURL(promVariables); + // update the URL + updateHistory({ + url: mergeUrlParams(parsedVariables, window.location.href), + title: document.title, + }); +}; + +/** * Returns a URL with no time range based on the current URL. * * @param {String} New URL @@ -133,6 +220,81 @@ export const timeRangeToUrl = (timeRange, url = window.location.href) => { }; /** + * Locates a panel (and its corresponding group) given a (URL) search query. Returns + * it as payload for the store to set the right expandaded panel. + * + * Params used to locate a panel are: + * - group: Group identifier + * - title: Panel title + * - y_label: Panel y_label + * + * @param {Object} dashboard - Dashboard reference from the Vuex store + * @param {String} search - URL location search query + * @returns {Object} payload - Payload for expanded panel to be displayed + * @returns {String} payload.group - Group where panel is located + * @returns {Object} payload.panel - Dashboard panel (graphData) reference + * @throws Will throw an error if Panel cannot be located. + */ +export const expandedPanelPayloadFromUrl = (dashboard, search = window.location.search) => { + const params = queryToObject(search); + + // Search for the panel if any of the search params is identified + if (params.group || params.title || params.y_label) { + const panelGroup = dashboard.panelGroups.find(({ group }) => params.group === group); + const panel = panelGroup.panels.find( + // eslint-disable-next-line babel/camelcase + ({ y_label, title }) => y_label === params.y_label && title === params.title, + ); + + if (!panel) { + // eslint-disable-next-line @gitlab/require-i18n-strings + throw new Error('Panel could no found by URL parameters.'); + } + return { group: panelGroup.group, panel }; + } + return null; +}; + +/** + * Convert panel information to a URL for the user to + * bookmark or share highlighting a specific panel. + * + * If no group/panel is set, the dashboard URL is returned. + * + * @param {?String} dashboard - Dashboard path, used as identifier for a dashboard + * @param {?Object} promVariables - Custom variables that came from the URL + * @param {?String} group - Group Identifier + * @param {?Object} panel - Panel object from the dashboard + * @param {?String} url - Base URL including current search params + * @returns Dashboard URL which expands a panel (chart) + */ +export const panelToUrl = ( + dashboard = null, + promVariables, + group, + panel, + url = window.location.href, +) => { + const params = { + dashboard, + ...promVariables, + }; + + if (group && panel) { + params.group = group; + params.title = panel.title; + params.y_label = panel.y_label; + } else { + // Remove existing parameters if any + params.group = null; + params.title = null; + params.y_label = null; + } + + return mergeUrlParams(params, url); +}; + +/** * Get the metric value from first data point. * Currently only used for bar charts * @@ -191,4 +353,39 @@ export const barChartsDataParser = (data = []) => {}, ); +/** + * Custom variables are defined in the dashboard yml file + * and their values can be passed through the URL. + * + * On component load, this method merges variables data + * from the yml file with URL data to store in the Vuex store. + * Not all params coming from the URL need to be stored. Only + * the ones that have a corresponding variable defined in the + * yml file. + * + * This ensures that there is always a single source of truth + * for variables + * + * This method can be improved further. See the below issue + * https://gitlab.com/gitlab-org/gitlab/-/issues/217713 + * + * @param {Object} varsFromYML template variables from yml file + * @returns {Object} + */ +export const mergeURLVariables = (varsFromYML = {}) => { + const varsFromURL = getPromCustomVariablesFromUrl(); + const variables = {}; + Object.keys(varsFromYML).forEach(key => { + if (Object.prototype.hasOwnProperty.call(varsFromURL, key)) { + variables[key] = { + ...varsFromYML[key], + value: varsFromURL[key], + }; + } else { + variables[key] = varsFromYML[key]; + } + }); + return variables; +}; + export default {}; |