Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/monitoring/components/variables/custom_variable.vue50
-rw-r--r--app/assets/javascripts/monitoring/components/variables/text_variable.vue39
-rw-r--r--app/assets/javascripts/monitoring/components/variables_section.vue62
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js5
-rw-r--r--app/assets/javascripts/monitoring/stores/getters.js6
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js9
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js24
-rw-r--r--app/assets/javascripts/monitoring/stores/variable_mapping.js16
-rw-r--r--app/assets/javascripts/monitoring/utils.js108
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue2
-rw-r--r--app/controllers/boards/issues_controller.rb3
-rw-r--r--app/controllers/concerns/boards_actions.rb3
-rw-r--r--app/models/performance_monitoring/prometheus_dashboard.rb2
-rw-r--r--changelogs/unreleased/213566-deploy-token-conan.yml5
-rw-r--r--changelogs/unreleased/dbodicherla-add-dropdown-input-based-on-dashboard-file.yml6
-rw-r--r--changelogs/unreleased/re-enable_boards_negative_filters.yml5
-rw-r--r--changelogs/unreleased/update-deprecated-slot-syntax-in---app-assets-javascripts-repository-comp.yml5
-rw-r--r--config/webpack.config.js16
-rw-r--r--doc/user/packages/conan_repository/index.md13
-rw-r--r--spec/features/boards/boards_spec.rb43
-rw-r--r--spec/frontend/monitoring/components/variables/custom_variable_spec.js52
-rw-r--r--spec/frontend/monitoring/components/variables/text_variable_spec.js59
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js75
-rw-r--r--spec/frontend/monitoring/mock_data.js15
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js34
-rw-r--r--spec/frontend/monitoring/store/getters_spec.js26
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js15
-rw-r--r--spec/frontend/monitoring/store/utils_spec.js22
-rw-r--r--spec/frontend/monitoring/utils_spec.js112
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);
+ });
+ });
});