diff options
30 files changed, 660 insertions, 175 deletions
diff --git a/app/assets/javascripts/monitoring/components/variables/custom_variable.vue b/app/assets/javascripts/monitoring/components/variables/custom_variable.vue new file mode 100644 index 00000000000..0ac7c0b80df --- /dev/null +++ b/app/assets/javascripts/monitoring/components/variables/custom_variable.vue @@ -0,0 +1,50 @@ +<script> +import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui'; + +export default { + components: { + GlFormGroup, + GlDropdown, + GlDropdownItem, + }, + props: { + name: { + type: String, + required: true, + }, + label: { + type: String, + required: true, + }, + value: { + type: String, + required: false, + default: '', + }, + options: { + type: Array, + required: true, + }, + }, + computed: { + defaultText() { + const selectedOpt = this.options.find(opt => opt.value === this.value); + return selectedOpt?.text || this.value; + }, + }, + methods: { + onUpdate(value) { + this.$emit('onUpdate', this.name, value); + }, + }, +}; +</script> +<template> + <gl-form-group :label="label"> + <gl-dropdown toggle-class="dropdown-menu-toggle" :text="defaultText"> + <gl-dropdown-item v-for="(opt, key) in options" :key="key" @click="onUpdate(opt.value)">{{ + opt.text + }}</gl-dropdown-item> + </gl-dropdown> + </gl-form-group> +</template> diff --git a/app/assets/javascripts/monitoring/components/variables/text_variable.vue b/app/assets/javascripts/monitoring/components/variables/text_variable.vue new file mode 100644 index 00000000000..ce0d19760e2 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/variables/text_variable.vue @@ -0,0 +1,39 @@ +<script> +import { GlFormGroup, GlFormInput } from '@gitlab/ui'; + +export default { + components: { + GlFormGroup, + GlFormInput, + }, + props: { + name: { + type: String, + required: true, + }, + label: { + type: String, + required: true, + }, + value: { + type: String, + required: true, + }, + }, + methods: { + onUpdate(event) { + this.$emit('onUpdate', this.name, event.target.value); + }, + }, +}; +</script> +<template> + <gl-form-group :label="label"> + <gl-form-input + :value="value" + :name="name" + @keyup.native.enter="onUpdate" + @blur.native="onUpdate" + /> + </gl-form-group> +</template> diff --git a/app/assets/javascripts/monitoring/components/variables_section.vue b/app/assets/javascripts/monitoring/components/variables_section.vue index 6d08bb4f219..e054c9d8e26 100644 --- a/app/assets/javascripts/monitoring/components/variables_section.vue +++ b/app/assets/javascripts/monitoring/components/variables_section.vue @@ -1,48 +1,56 @@ <script> -import { GlFormGroup, GlFormInput } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; -import { mergeUrlParams, updateHistory } from '~/lib/utils/url_utility'; +import CustomVariable from './variables/custom_variable.vue'; +import TextVariable from './variables/text_variable.vue'; +import { setPromCustomVariablesFromUrl } from '../utils'; export default { components: { - GlFormGroup, - GlFormInput, + CustomVariable, + TextVariable, }, computed: { ...mapState('monitoringDashboard', ['promVariables']), }, methods: { - ...mapActions('monitoringDashboard', ['fetchDashboardData', 'setVariableValues']), - refreshDashboard(event) { - const { name, value } = event.target; - - if (this.promVariables[name] !== value) { - const changedVariable = { [name]: value }; - - this.setVariableValues(changedVariable); - - updateHistory({ - url: mergeUrlParams(this.promVariables, window.location.href), - title: document.title, - }); - + ...mapActions('monitoringDashboard', ['fetchDashboardData', 'updateVariableValues']), + refreshDashboard(variable, value) { + if (this.promVariables[variable].value !== value) { + const changedVariable = { key: variable, value }; + // update the Vuex store + this.updateVariableValues(changedVariable); + // the below calls can ideally be moved out of the + // component and into the actions and let the + // mutation respond directly. + // This can be further investigate in + // https://gitlab.com/gitlab-org/gitlab/-/issues/217713 + setPromCustomVariablesFromUrl(this.promVariables); + // fetch data this.fetchDashboardData(); } }, + variableComponent(type) { + const types = { + text: TextVariable, + custom: CustomVariable, + }; + return types[type] || TextVariable; + }, }, }; </script> <template> <div ref="variablesSection" class="d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 variables-section"> - <div v-for="(val, key) in promVariables" :key="key" class="mb-1 pr-2 d-flex d-sm-block"> - <gl-form-group :label="key" class="mb-0 flex-grow-1"> - <gl-form-input - :value="val" - :name="key" - @keyup.native.enter="refreshDashboard" - @blur.native="refreshDashboard" - /> - </gl-form-group> + <div v-for="(variable, key) in promVariables" :key="key" class="mb-1 pr-2 d-flex d-sm-block"> + <component + :is="variableComponent(variable.type)" + class="mb-0 flex-grow-1" + :label="variable.label" + :value="variable.value" + :name="key" + :options="variable.options" + @onUpdate="refreshDashboard" + /> </div> </div> </template> diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 3d2cabfd978..2bbf9ef9d78 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -4,7 +4,6 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import { getParameterValues } from '~/lib/utils/url_utility'; import store from './stores'; -import { promCustomVariablesFromUrl } from './utils'; Vue.use(GlToast); @@ -14,8 +13,6 @@ export default (props = {}) => { if (el && el.dataset) { const [currentDashboard] = getParameterValues('dashboard'); - store.dispatch('monitoringDashboard/setVariableValues', promCustomVariablesFromUrl()); - // eslint-disable-next-line no-new new Vue({ el, diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 7df18f5d6d7..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, @@ -159,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'); @@ -413,7 +416,7 @@ export const duplicateSystemDashboard = ({ state }, payload) => { // Variables manipulation -export const setVariableValues = ({ commit }, updatedVariable) => { +export const updateVariableValues = ({ commit }, updatedVariable) => { commit(types.UPDATE_VARIABLE_VALUES, updatedVariable); }; diff --git a/app/assets/javascripts/monitoring/stores/getters.js b/app/assets/javascripts/monitoring/stores/getters.js index 63a7d0c643d..ae3ff5596e1 100644 --- a/app/assets/javascripts/monitoring/stores/getters.js +++ b/app/assets/javascripts/monitoring/stores/getters.js @@ -1,5 +1,4 @@ import { flatMap } from 'lodash'; -import { removePrefixFromLabels } from './utils'; import { NOT_IN_DB_PREFIX } from '../constants'; const metricsIdsInPanel = panel => @@ -123,10 +122,7 @@ export const filteredEnvironments = state => */ export const getCustomVariablesArray = state => - flatMap(state.promVariables, (val, key) => [ - encodeURIComponent(removePrefixFromLabels(key)), - encodeURIComponent(val), - ]); + 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/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index c5db94cb267..f41cf3fc477 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -191,11 +191,10 @@ export default { [types.SET_VARIABLES](state, variables) { state.promVariables = variables; }, - [types.UPDATE_VARIABLE_VALUES](state, newVariable) { - Object.keys(newVariable).forEach(key => { - if (Object.prototype.hasOwnProperty.call(state.promVariables, key)) { - state.promVariables[key] = newVariable[key]; - } + [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/utils.js b/app/assets/javascripts/monitoring/stores/utils.js index c92b42fefad..a47e5f598f5 100644 --- a/app/assets/javascripts/monitoring/stores/utils.js +++ b/app/assets/javascripts/monitoring/stores/utils.js @@ -2,7 +2,7 @@ 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, VARIABLE_PREFIX } from '../constants'; +import { NOT_IN_DB_PREFIX } from '../constants'; export const gqClient = createGqClient( {}, @@ -229,25 +229,3 @@ export const normalizeQueryResult = timeSeries => { return normalizedResult; }; - -/** - * 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 addPrefixToLabels = 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 removePrefixFromLabels = label => - (label || '').replace(new RegExp(`^${VARIABLE_PREFIX}`), ''); diff --git a/app/assets/javascripts/monitoring/stores/variable_mapping.js b/app/assets/javascripts/monitoring/stores/variable_mapping.js index 0e49fc784fe..bfb469da19e 100644 --- a/app/assets/javascripts/monitoring/stores/variable_mapping.js +++ b/app/assets/javascripts/monitoring/stores/variable_mapping.js @@ -1,5 +1,4 @@ import { isString } from 'lodash'; -import { addPrefixToLabels } from './utils'; import { VARIABLE_TYPES } from '../constants'; /** @@ -56,15 +55,20 @@ const normalizeCustomVariableOptions = ({ default: defaultOpt = false, text, val * 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 ?? []; + 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, - options: options.map(normalizeCustomVariableOptions), + value: defaultOpt?.value, + options, }; }; @@ -83,6 +87,9 @@ const parseSimpleCustomOptions = opt => ({ text: opt, value: opt }); * * 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} */ @@ -90,6 +97,7 @@ const customSimpleVariableParser = simpleVar => { const options = (simpleVar || []).map(parseSimpleCustomOptions); return { type: VARIABLE_TYPES.custom, + value: options[0].value, label: null, options: options.map(normalizeCustomVariableOptions), }; @@ -150,7 +158,7 @@ export const parseTemplatingVariables = ({ variables = {} } = {}) => if (parsedVar) { acc[key] = { ...parsedVar, - label: addPrefixToLabels(parsedVar.label || key), + label: parsedVar.label || key, }; } return acc; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 4c714a684e9..2e4012855d5 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -1,5 +1,10 @@ -import { pickBy } from 'lodash'; -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, @@ -123,6 +128,44 @@ 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. @@ -131,8 +174,30 @@ export const timeRangeFromUrl = (search = window.location.search) => { * @returns {Object} The custom variables defined by the user in the URL */ -export const promCustomVariablesFromUrl = (search = window.location.search) => - pickBy(queryToObject(search), (val, key) => key.startsWith(VARIABLE_PREFIX)); +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. @@ -280,4 +345,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 {}; diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index 886e9d76cca..45c343c3f7f 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -242,7 +242,7 @@ export default { </li> <li v-if="renderAddToTreeDropdown" class="breadcrumb-item"> <gl-dropdown toggle-class="add-to-tree qa-add-to-tree ml-1"> - <template slot="button-content"> + <template #button-content> <span class="sr-only">{{ __('Add to tree') }}</span> <icon name="plus" :size="16" class="float-left" /> <icon name="chevron-down" :size="16" class="float-left" /> diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index bad55b34fb3..a18c80b996e 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -20,9 +20,6 @@ module Boards skip_before_action :authenticate_user!, only: [:index] before_action :validate_id_list, only: [:bulk_move] before_action :can_move_issues?, only: [:bulk_move] - before_action do - push_frontend_feature_flag(:not_issuable_queries, board.group, default_enabled: true) - end def index list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params) diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb index eb1080cb3d2..9d40b9e8c88 100644 --- a/app/controllers/concerns/boards_actions.rb +++ b/app/controllers/concerns/boards_actions.rb @@ -10,6 +10,9 @@ module BoardsActions before_action :boards, only: :index before_action :board, only: :show before_action :push_wip_limits, only: [:index, :show] + before_action do + push_frontend_feature_flag(:not_issuable_queries, parent, default_enabled: true) + end end def index diff --git a/app/models/performance_monitoring/prometheus_dashboard.rb b/app/models/performance_monitoring/prometheus_dashboard.rb index 30fb1935a27..57222c61b36 100644 --- a/app/models/performance_monitoring/prometheus_dashboard.rb +++ b/app/models/performance_monitoring/prometheus_dashboard.rb @@ -4,7 +4,7 @@ module PerformanceMonitoring class PrometheusDashboard include ActiveModel::Model - attr_accessor :dashboard, :panel_groups, :path, :environment, :priority + attr_accessor :dashboard, :panel_groups, :path, :environment, :priority, :templating validates :dashboard, presence: true validates :panel_groups, presence: true diff --git a/changelogs/unreleased/213566-deploy-token-conan.yml b/changelogs/unreleased/213566-deploy-token-conan.yml new file mode 100644 index 00000000000..7f06fa3d7f7 --- /dev/null +++ b/changelogs/unreleased/213566-deploy-token-conan.yml @@ -0,0 +1,5 @@ +--- +title: Conan registry is accessible using deploy tokens +merge_request: 31114 +author: +type: added diff --git a/changelogs/unreleased/dbodicherla-add-dropdown-input-based-on-dashboard-file.yml b/changelogs/unreleased/dbodicherla-add-dropdown-input-based-on-dashboard-file.yml new file mode 100644 index 00000000000..befae5dcb5b --- /dev/null +++ b/changelogs/unreleased/dbodicherla-add-dropdown-input-based-on-dashboard-file.yml @@ -0,0 +1,6 @@ +--- +title: Render dropdown and text elements based on variables defined in monitoring + dashboard yml file +merge_request: 31524 +author: +type: added diff --git a/changelogs/unreleased/re-enable_boards_negative_filters.yml b/changelogs/unreleased/re-enable_boards_negative_filters.yml new file mode 100644 index 00000000000..3a916ea054f --- /dev/null +++ b/changelogs/unreleased/re-enable_boards_negative_filters.yml @@ -0,0 +1,5 @@ +--- +title: Re-enable negative filters for Boards +merge_request: 32348 +author: +type: fixed diff --git a/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-repository-comp.yml b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-repository-comp.yml new file mode 100644 index 00000000000..7ca74c01be4 --- /dev/null +++ b/changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-repository-comp.yml @@ -0,0 +1,5 @@ +--- +title: Update deprecated slot syntax in ./app/assets/javascripts/repository/components/breadcrumbs.vue +merge_request: 32017 +author: Gilang Gumilar +type: changed diff --git a/config/webpack.config.js b/config/webpack.config.js index 342a4da5170..be670515267 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -141,8 +141,8 @@ module.exports = { output: { path: path.join(ROOT_PATH, 'public/assets/webpack'), publicPath: '/assets/webpack/', - filename: IS_PRODUCTION ? '[name].[chunkhash:8].bundle.js' : '[name].bundle.js', - chunkFilename: IS_PRODUCTION ? '[name].[chunkhash:8].chunk.js' : '[name].chunk.js', + filename: IS_PRODUCTION ? '[name].[contenthash:8].bundle.js' : '[name].bundle.js', + chunkFilename: IS_PRODUCTION ? '[name].[contenthash:8].chunk.js' : '[name].chunk.js', globalObject: 'this', // allow HMR and web workers to play nice }, @@ -191,7 +191,7 @@ module.exports = { test: /icons\.svg$/, loader: 'file-loader', options: { - name: '[name].[hash:8].[ext]', + name: '[name].[contenthash:8].[ext]', }, }, { @@ -210,7 +210,7 @@ module.exports = { { loader: 'worker-loader', options: { - name: '[name].[hash:8].worker.js', + name: '[name].[contenthash:8].worker.js', inline: IS_DEV_SERVER, }, }, @@ -222,7 +222,7 @@ module.exports = { exclude: /node_modules/, loader: 'file-loader', options: { - name: '[name].[hash:8].[ext]', + name: '[name].[contenthash:8].[ext]', }, }, { @@ -232,7 +232,7 @@ module.exports = { { loader: 'css-loader', options: { - name: '[name].[hash:8].[ext]', + name: '[name].[contenthash:8].[ext]', }, }, ], @@ -242,13 +242,15 @@ module.exports = { include: /node_modules\/katex\/dist\/fonts/, loader: 'file-loader', options: { - name: '[name].[hash:8].[ext]', + name: '[name].[contenthash:8].[ext]', }, }, ], }, optimization: { + // Replace 'hashed' with 'deterministic' in webpack 5 + moduleIds: 'hashed', runtimeChunk: 'single', splitChunks: { maxInitialRequests: 4, diff --git a/doc/user/packages/conan_repository/index.md b/doc/user/packages/conan_repository/index.md index 2c7b13d3164..5f84d9a5ba8 100644 --- a/doc/user/packages/conan_repository/index.md +++ b/doc/user/packages/conan_repository/index.md @@ -108,14 +108,19 @@ conan search Hello* --all --remote=gitlab ## Authenticating to the GitLab Conan Repository -You will need to generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api` for repository authentication. +You will need a personal access token or deploy token. + +For repository authentication: + +- You can generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`. +- You can generate a [deploy token](./../../project/deploy_tokens/index.md) with the scope set to `read_package_registry`, `write_package_registry`, or both. ### Adding a Conan user to the GitLab remote Once you have a personal access token and have [set your Conan remote](#adding-the-gitlab-package-registry-as-a-conan-remote), you can associate the token with the remote so you do not have to explicitly add them to each Conan command you run: ```shell -conan user <gitlab-username> -r gitlab -p <personal_access_token> +conan user <gitlab_username or deploy_token_username> -r gitlab -p <personal_access_token or deploy_token> ``` Note: **Note** @@ -130,7 +135,7 @@ Alternatively, you could explicitly include your credentials in any given comman For example: ```shell -CONAN_LOGIN_USERNAME=<gitlab-username> CONAN_PASSWORD=<personal_access_token> conan upload Hello/0.1@my-group+my-project/beta --all --remote=gitlab +CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=<personal_access_token or deploy_token> conan upload Hello/0.1@my-group+my-project/beta --all --remote=gitlab ``` ### Setting a default remote to your project (optional) @@ -148,7 +153,7 @@ This functionality is best suited for when you want to consume or install packag The rest of the example commands in this documentation assume that you have added a Conan user with your credentials to the `gitlab` remote and will not include the explicit credentials or remote option, but be aware that any of the commands could be run without having added a user or default remote: ```shell -`CONAN_LOGIN_USERNAME=<gitlab-username> CONAN_PASSWORD=<personal_access_token> <conan command> --remote=gitlab +`CONAN_LOGIN_USERNAME=<gitlab_username or deploy_token_username> CONAN_PASSWORD=<personal_access_token or deploy_token> <conan command> --remote=gitlab ``` ## Uploading a package diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index cc0fdfbe278..e82b1be4310 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -137,6 +137,49 @@ describe 'Issue Boards', :js do expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 0) end + context 'search list negation queries' do + context 'with the NOT queries feature flag disabled' do + before do + stub_feature_flags(not_issuable_queries: false) + visit project_board_path(project, board) + end + + it 'does not have the != option' do + find('.filtered-search').set('label:') + + wait_for_requests + within('#js-dropdown-operator') do + tokens = all(:css, 'li.filter-dropdown-item') + expect(tokens.count).to eq(1) + button = tokens[0].find('button') + expect(button).to have_content('=') + expect(button).not_to have_content('!=') + end + end + end + + context 'with the NOT queries feature flag enabled' do + before do + stub_feature_flags(not_issuable_queries: true) + visit project_board_path(project, board) + end + + it 'does not have the != option' do + find('.filtered-search').set('label:') + + wait_for_requests + within('#js-dropdown-operator') do + tokens = all(:css, 'li.filter-dropdown-item') + expect(tokens.count).to eq(2) + button = tokens[0].find('button') + expect(button).to have_content('=') + button = tokens[1].find('button') + expect(button).to have_content('!=') + end + end + end + end + it 'allows user to delete board' do page.within(find('.board:nth-child(2)')) do accept_confirm { find('.board-delete').click } diff --git a/spec/frontend/monitoring/components/variables/custom_variable_spec.js b/spec/frontend/monitoring/components/variables/custom_variable_spec.js new file mode 100644 index 00000000000..5a2b26219b6 --- /dev/null +++ b/spec/frontend/monitoring/components/variables/custom_variable_spec.js @@ -0,0 +1,52 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import CustomVariable from '~/monitoring/components/variables/custom_variable.vue'; + +describe('Custom variable component', () => { + let wrapper; + const propsData = { + name: 'env', + label: 'Select environment', + value: 'Production', + options: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }], + }; + const createShallowWrapper = () => { + wrapper = shallowMount(CustomVariable, { + propsData, + }); + }; + + const findDropdown = () => wrapper.find(GlDropdown); + const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + + it('renders dropdown element when all necessary props are passed', () => { + createShallowWrapper(); + + expect(findDropdown()).toExist(); + }); + + it('renders dropdown element with a text', () => { + createShallowWrapper(); + + expect(findDropdown().attributes('text')).toBe(propsData.value); + }); + + it('renders all the dropdown items', () => { + createShallowWrapper(); + + expect(findDropdownItems()).toHaveLength(propsData.options.length); + }); + + it('changing dropdown items triggers update', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findDropdownItems() + .at(1) + .vm.$emit('click'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/variables/text_variable_spec.js b/spec/frontend/monitoring/components/variables/text_variable_spec.js new file mode 100644 index 00000000000..f01584ae8bc --- /dev/null +++ b/spec/frontend/monitoring/components/variables/text_variable_spec.js @@ -0,0 +1,59 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlFormInput } from '@gitlab/ui'; +import TextVariable from '~/monitoring/components/variables/text_variable.vue'; + +describe('Text variable component', () => { + let wrapper; + const propsData = { + name: 'pod', + label: 'Select pod', + value: 'test-pod', + }; + const createShallowWrapper = () => { + wrapper = shallowMount(TextVariable, { + propsData, + }); + }; + + const findInput = () => wrapper.find(GlFormInput); + + it('renders a text input when all props are passed', () => { + createShallowWrapper(); + + expect(findInput()).toExist(); + }); + + it('always has a default value', () => { + createShallowWrapper(); + + return wrapper.vm.$nextTick(() => { + expect(findInput().attributes('value')).toBe(propsData.value); + }); + }); + + it('triggers keyup enter', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findInput().element.value = 'prod-pod'; + findInput().trigger('input'); + findInput().trigger('keyup.enter'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod'); + }); + }); + + it('triggers blur enter', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findInput().element.value = 'canary-pod'; + findInput().trigger('input'); + findInput().trigger('blur'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js index 6bd8b18881b..2fb7b753de0 100644 --- a/spec/frontend/monitoring/components/variables_section_spec.js +++ b/spec/frontend/monitoring/components/variables_section_spec.js @@ -1,10 +1,13 @@ import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlFormInput } from '@gitlab/ui'; import VariablesSection from '~/monitoring/components/variables_section.vue'; +import CustomVariable from '~/monitoring/components/variables/custom_variable.vue'; +import TextVariable from '~/monitoring/components/variables/text_variable.vue'; import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility'; import { createStore } from '~/monitoring/stores'; +import { convertVariablesForURL } from '~/monitoring/utils'; import * as types from '~/monitoring/stores/mutation_types'; +import { mockTemplatingDataResponses } from '../mock_data'; jest.mock('~/lib/utils/url_utility', () => ({ updateHistory: jest.fn(), @@ -15,8 +18,9 @@ describe('Metrics dashboard/variables section component', () => { let store; let wrapper; const sampleVariables = { - 'var-label1': 'pod', - 'var-label2': 'main', + label1: mockTemplatingDataResponses.simpleText.simpleText, + label2: mockTemplatingDataResponses.advText.advText, + label3: mockTemplatingDataResponses.simpleCustom.simpleCustom, }; const createShallowWrapper = () => { @@ -25,8 +29,8 @@ describe('Metrics dashboard/variables section component', () => { }); }; - const findAllFormInputs = () => wrapper.findAll(GlFormInput); - const getInputAt = i => findAllFormInputs().at(i); + const findTextInput = () => wrapper.findAll(TextVariable); + const findCustomInput = () => wrapper.findAll(CustomVariable); beforeEach(() => { store = createStore(); @@ -36,9 +40,9 @@ describe('Metrics dashboard/variables section component', () => { it('does not show the variables section', () => { createShallowWrapper(); - const allInputs = findAllFormInputs(); + const allInputs = findTextInput().length + findCustomInput().length; - expect(allInputs).toHaveLength(0); + expect(allInputs).toBe(0); }); it('shows the variables section', () => { @@ -46,15 +50,15 @@ describe('Metrics dashboard/variables section component', () => { wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables); return wrapper.vm.$nextTick(() => { - const allInputs = findAllFormInputs(); + const allInputs = findTextInput().length + findCustomInput().length; - expect(allInputs).toHaveLength(Object.keys(sampleVariables).length); + expect(allInputs).toBe(Object.keys(sampleVariables).length); }); }); describe('when changing the variable inputs', () => { const fetchDashboardData = jest.fn(); - const setVariableValues = jest.fn(); + const updateVariableValues = jest.fn(); beforeEach(() => { store = new Vuex.Store({ @@ -67,7 +71,7 @@ describe('Metrics dashboard/variables section component', () => { }, actions: { fetchDashboardData, - setVariableValues, + updateVariableValues, }, }, }, @@ -76,39 +80,44 @@ describe('Metrics dashboard/variables section component', () => { createShallowWrapper(); }); - it('merges the url params and refreshes the dashboard when a form input is blurred', () => { - const firstInput = getInputAt(0); + it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => { + const firstInput = findTextInput().at(0); - firstInput.element.value = 'POD'; - firstInput.vm.$emit('input'); - firstInput.trigger('blur'); + firstInput.vm.$emit('onUpdate', 'label1', 'test'); - expect(setVariableValues).toHaveBeenCalled(); - expect(mergeUrlParams).toHaveBeenCalledWith(sampleVariables, window.location.href); - expect(updateHistory).toHaveBeenCalled(); - expect(fetchDashboardData).toHaveBeenCalled(); + return wrapper.vm.$nextTick(() => { + expect(updateVariableValues).toHaveBeenCalled(); + expect(mergeUrlParams).toHaveBeenCalledWith( + convertVariablesForURL(sampleVariables), + window.location.href, + ); + expect(updateHistory).toHaveBeenCalled(); + expect(fetchDashboardData).toHaveBeenCalled(); + }); }); - it('merges the url params and refreshes the dashboard when a form input has received an enter key press', () => { - const firstInput = getInputAt(0); + it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => { + const firstInput = findCustomInput().at(0); - firstInput.element.value = 'POD'; - firstInput.vm.$emit('input'); - firstInput.trigger('keyup.enter'); + firstInput.vm.$emit('onUpdate', 'label1', 'test'); - expect(setVariableValues).toHaveBeenCalled(); - expect(mergeUrlParams).toHaveBeenCalledWith(sampleVariables, window.location.href); - expect(updateHistory).toHaveBeenCalled(); - expect(fetchDashboardData).toHaveBeenCalled(); + return wrapper.vm.$nextTick(() => { + expect(updateVariableValues).toHaveBeenCalled(); + expect(mergeUrlParams).toHaveBeenCalledWith( + convertVariablesForURL(sampleVariables), + window.location.href, + ); + expect(updateHistory).toHaveBeenCalled(); + expect(fetchDashboardData).toHaveBeenCalled(); + }); }); it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => { - const firstInput = getInputAt(0); + const firstInput = findTextInput().at(0); - firstInput.vm.$emit('input'); - firstInput.trigger('keyup.enter'); + firstInput.vm.$emit('onUpdate', 'label1', 'Simple text'); - expect(setVariableValues).not.toHaveBeenCalled(); + expect(updateVariableValues).not.toHaveBeenCalled(); expect(mergeUrlParams).not.toHaveBeenCalled(); expect(updateHistory).not.toHaveBeenCalled(); expect(fetchDashboardData).not.toHaveBeenCalled(); diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index a8443c08b00..4611e6f1b18 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -642,7 +642,7 @@ const generateMockTemplatingData = data => { const responseForSimpleTextVariable = { simpleText: { - label: 'var-simpleText', + label: 'simpleText', type: 'text', value: 'Simple text', }, @@ -650,7 +650,7 @@ const responseForSimpleTextVariable = { const responseForAdvTextVariable = { advText: { - label: 'var-Variable 4', + label: 'Variable 4', type: 'text', value: 'default', }, @@ -658,7 +658,8 @@ const responseForAdvTextVariable = { const responseForSimpleCustomVariable = { simpleCustom: { - label: 'var-simpleCustom', + label: 'simpleCustom', + value: 'value1', options: [ { default: false, @@ -682,7 +683,7 @@ const responseForSimpleCustomVariable = { const responseForAdvancedCustomVariableWithoutOptions = { advCustomWithoutOpts: { - label: 'var-advCustomWithoutOpts', + label: 'advCustomWithoutOpts', options: [], type: 'custom', }, @@ -690,7 +691,8 @@ const responseForAdvancedCustomVariableWithoutOptions = { const responseForAdvancedCustomVariableWithoutLabel = { advCustomWithoutLabel: { - label: 'var-advCustomWithoutLabel', + label: 'advCustomWithoutLabel', + value: 'value2', options: [ { default: false, @@ -710,7 +712,8 @@ const responseForAdvancedCustomVariableWithoutLabel = { const responseForAdvancedCustomVariable = { ...responseForSimpleCustomVariable, advCustomNormal: { - label: 'var-Advanced Var', + label: 'Advanced Var', + value: 'value2', options: [ { default: false, diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 32ff03a53d7..8914f2e66ea 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -26,7 +26,7 @@ import { clearExpandedPanel, setGettingStartedEmptyState, duplicateSystemDashboard, - setVariableValues, + updateVariableValues, } from '~/monitoring/stores/actions'; import { gqClient, @@ -40,6 +40,7 @@ import { deploymentData, environmentData, annotationsData, + mockTemplatingData, dashboardGitResponse, mockDashboardsErrorResponse, } from '../mock_data'; @@ -442,14 +443,14 @@ describe('Monitoring store actions', () => { }); }); - describe('setVariableValues', () => { + describe('updateVariableValues', () => { let mockedState; beforeEach(() => { mockedState = storeState(); }); it('should commit UPDATE_VARIABLE_VALUES mutation', done => { testAction( - setVariableValues, + updateVariableValues, { pod: 'POD' }, mockedState, [ @@ -574,6 +575,33 @@ describe('Monitoring store actions', () => { ); expect(dispatch).toHaveBeenCalledWith('fetchDashboardData'); }); + + it('stores templating variables', () => { + const response = { + ...metricsDashboardResponse.dashboard, + ...mockTemplatingData.allVariableTypes.dashboard, + }; + + receiveMetricsDashboardSuccess( + { state, commit, dispatch }, + { + response: { + ...metricsDashboardResponse, + dashboard: { + ...metricsDashboardResponse.dashboard, + ...mockTemplatingData.allVariableTypes.dashboard, + }, + }, + }, + ); + + expect(commit).toHaveBeenCalledWith( + types.RECEIVE_METRICS_DASHBOARD_SUCCESS, + + response, + ); + }); + it('sets the dashboards loaded from the repository', () => { const params = {}; const response = metricsDashboardResponse; diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js index 3577bebb336..365052e68e3 100644 --- a/spec/frontend/monitoring/store/getters_spec.js +++ b/spec/frontend/monitoring/store/getters_spec.js @@ -3,7 +3,12 @@ import * as getters from '~/monitoring/stores/getters'; import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import { metricStates } from '~/monitoring/constants'; -import { environmentData, metricsResult, dashboardGitResponse } from '../mock_data'; +import { + environmentData, + metricsResult, + dashboardGitResponse, + mockTemplatingDataResponses, +} from '../mock_data'; import { metricsDashboardPayload, metricResultStatus, @@ -326,10 +331,6 @@ describe('Monitoring store Getters', () => { describe('getCustomVariablesArray', () => { let state; - const sampleVariables = { - 'var-label1': 'pod', - 'var-label2': 'env', - }; beforeEach(() => { state = { @@ -337,11 +338,20 @@ describe('Monitoring store Getters', () => { }; }); - it('transforms the promVariables object to an array in the [variable, variable_value] format', () => { - mutations[types.SET_VARIABLES](state, sampleVariables); + it('transforms the promVariables object to an array in the [variable, variable_value] format for all variable types', () => { + mutations[types.SET_VARIABLES](state, mockTemplatingDataResponses.allVariableTypes); const variablesArray = getters.getCustomVariablesArray(state); - expect(variablesArray).toEqual(['label1', 'pod', 'label2', 'env']); + expect(variablesArray).toEqual([ + 'simpleText', + 'Simple text', + 'advText', + 'default', + 'simpleCustom', + 'value1', + 'advCustomNormal', + 'value2', + ]); }); it('transforms the promVariables object to an empty array when no keys are present', () => { diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index 6c9b0726c93..4306243689a 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -427,18 +427,11 @@ describe('Monitoring mutations', () => { mutations[types.SET_VARIABLES](stateCopy, {}); }); - it('ignores updates that are not already in promVariables', () => { - mutations[types.SET_VARIABLES](stateCopy, { environment: 'prod' }); - mutations[types.UPDATE_VARIABLE_VALUES](stateCopy, { pod: 'new pod' }); + it('updates only the value of the variable in promVariables', () => { + mutations[types.SET_VARIABLES](stateCopy, { environment: { value: 'prod', type: 'text' } }); + mutations[types.UPDATE_VARIABLE_VALUES](stateCopy, { key: 'environment', value: 'new prod' }); - expect(stateCopy.promVariables).toEqual({ environment: 'prod' }); - }); - - it('only updates existing variables', () => { - mutations[types.SET_VARIABLES](stateCopy, { pod: 'POD' }); - mutations[types.UPDATE_VARIABLE_VALUES](stateCopy, { pod: 'new pod' }); - - expect(stateCopy.promVariables).toEqual({ pod: 'new pod' }); + expect(stateCopy.promVariables).toEqual({ environment: { value: 'new prod', type: 'text' } }); }); }); }); diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js index af040743ebb..fe5754e1216 100644 --- a/spec/frontend/monitoring/store/utils_spec.js +++ b/spec/frontend/monitoring/store/utils_spec.js @@ -5,7 +5,6 @@ import { parseAnnotationsResponse, removeLeadingSlash, mapToDashboardViewModel, - removePrefixFromLabels, } from '~/monitoring/stores/utils'; import { annotationsData } from '../mock_data'; import { NOT_IN_DB_PREFIX } from '~/monitoring/constants'; @@ -420,24 +419,3 @@ describe('removeLeadingSlash', () => { }); }); }); - -describe('removePrefixFromLabels', () => { - it.each` - input | expected - ${undefined} | ${''} - ${null} | ${''} - ${''} | ${''} - ${' '} | ${' '} - ${'pod-1'} | ${'pod-1'} - ${'pod-var-1'} | ${'pod-var-1'} - ${'pod-1-var'} | ${'pod-1-var'} - ${'podvar--1'} | ${'podvar--1'} - ${'povar-d-1'} | ${'povar-d-1'} - ${'var-pod-1'} | ${'pod-1'} - ${'var-var-pod-1'} | ${'var-pod-1'} - ${'varvar-pod-1'} | ${'varvar-pod-1'} - ${'var-pod-1-var-'} | ${'pod-1-var-'} - `('removePrefixFromLabels returns $expected with input $input', ({ input, expected }) => { - expect(removePrefixFromLabels(input)).toEqual(expected); - }); -}); diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js index 52fd776e67e..e78c17dc392 100644 --- a/spec/frontend/monitoring/utils_spec.js +++ b/spec/frontend/monitoring/utils_spec.js @@ -169,8 +169,8 @@ describe('monitoring/utils', () => { }); }); - describe('promCustomVariablesFromUrl', () => { - const { promCustomVariablesFromUrl } = monitoringUtils; + describe('getPromCustomVariablesFromUrl', () => { + const { getPromCustomVariablesFromUrl } = monitoringUtils; beforeEach(() => { jest.spyOn(urlUtils, 'queryToObject'); @@ -195,7 +195,7 @@ describe('monitoring/utils', () => { 'var-pod': 'POD', }); - expect(promCustomVariablesFromUrl()).toEqual(expect.objectContaining({ 'var-pod': 'POD' })); + expect(getPromCustomVariablesFromUrl()).toEqual(expect.objectContaining({ pod: 'POD' })); }); it('returns an empty object when no custom variables are present', () => { @@ -203,7 +203,7 @@ describe('monitoring/utils', () => { dashboard: '.gitlab/dashboards/custom_dashboard.yml', }); - expect(promCustomVariablesFromUrl()).toStrictEqual({}); + expect(getPromCustomVariablesFromUrl()).toStrictEqual({}); }); }); @@ -398,4 +398,108 @@ describe('monitoring/utils', () => { }); }); }); + + describe('removePrefixFromLabel', () => { + it.each` + input | expected + ${undefined} | ${''} + ${null} | ${''} + ${''} | ${''} + ${' '} | ${' '} + ${'pod-1'} | ${'pod-1'} + ${'pod-var-1'} | ${'pod-var-1'} + ${'pod-1-var'} | ${'pod-1-var'} + ${'podvar--1'} | ${'podvar--1'} + ${'povar-d-1'} | ${'povar-d-1'} + ${'var-pod-1'} | ${'pod-1'} + ${'var-var-pod-1'} | ${'var-pod-1'} + ${'varvar-pod-1'} | ${'varvar-pod-1'} + ${'var-pod-1-var-'} | ${'pod-1-var-'} + `('removePrefixFromLabel returns $expected with input $input', ({ input, expected }) => { + expect(monitoringUtils.removePrefixFromLabel(input)).toEqual(expected); + }); + }); + + describe('mergeURLVariables', () => { + beforeEach(() => { + jest.spyOn(urlUtils, 'queryToObject'); + }); + + afterEach(() => { + urlUtils.queryToObject.mockRestore(); + }); + + it('returns empty object if variables are not defined in yml or URL', () => { + urlUtils.queryToObject.mockReturnValueOnce({}); + + expect(monitoringUtils.mergeURLVariables({})).toEqual({}); + }); + + it('returns empty object if variables are defined in URL but not in yml', () => { + urlUtils.queryToObject.mockReturnValueOnce({ + 'var-env': 'one', + 'var-instance': 'localhost', + }); + + expect(monitoringUtils.mergeURLVariables({})).toEqual({}); + }); + + it('returns yml variables if variables defined in yml but not in the URL', () => { + urlUtils.queryToObject.mockReturnValueOnce({}); + + const params = { + env: 'one', + instance: 'localhost', + }; + + expect(monitoringUtils.mergeURLVariables(params)).toEqual(params); + }); + + it('returns yml variables if variables defined in URL do not match with yml variables', () => { + const urlParams = { + 'var-env': 'one', + 'var-instance': 'localhost', + }; + const ymlParams = { + pod: { value: 'one' }, + service: { value: 'database' }, + }; + urlUtils.queryToObject.mockReturnValueOnce(urlParams); + + expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(ymlParams); + }); + + it('returns merged yml and URL variables if there is some match', () => { + const urlParams = { + 'var-env': 'one', + 'var-instance': 'localhost:8080', + }; + const ymlParams = { + instance: { value: 'localhost' }, + service: { value: 'database' }, + }; + + const merged = { + instance: { value: 'localhost:8080' }, + service: { value: 'database' }, + }; + + urlUtils.queryToObject.mockReturnValueOnce(urlParams); + + expect(monitoringUtils.mergeURLVariables(ymlParams)).toEqual(merged); + }); + }); + + describe('convertVariablesForURL', () => { + it.each` + input | expected + ${undefined} | ${{}} + ${null} | ${{}} + ${{}} | ${{}} + ${{ env: { value: 'prod' } }} | ${{ 'var-env': 'prod' }} + ${{ 'var-env': { value: 'prod' } }} | ${{ 'var-var-env': 'prod' }} + `('convertVariablesForURL returns $expected with input $input', ({ input, expected }) => { + expect(monitoringUtils.convertVariablesForURL(input)).toEqual(expected); + }); + }); }); |