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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-19 15:12:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-19 15:12:07 +0300
commit6b19945915303e04fcca21405bca0cd94125199c (patch)
tree2ea89b55d5e419023bd25a19c5633fefa647dcfb
parent811f549164618e3607721eecbfd76e292555b339 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml1
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js40
-rw-r--r--app/assets/javascripts/diffs/components/app.vue9
-rw-r--r--app/assets/javascripts/pages/projects/work_items/index.js (renamed from app/assets/javascripts/pages/projects/work_items/index/index.js)0
-rw-r--r--app/assets/javascripts/project_visibility.js62
-rw-r--r--app/assets/javascripts/projects/new/components/new_project_url_select.vue27
-rw-r--r--app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql3
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js3
-rw-r--r--app/assets/javascripts/work_items/components/app.vue10
-rw-r--r--app/assets/javascripts/work_items/constants.js3
-rw-r--r--app/assets/javascripts/work_items/graphql/fragmentTypes.json1
-rw-r--r--app/assets/javascripts/work_items/graphql/provider.js54
-rw-r--r--app/assets/javascripts/work_items/graphql/resolvers.js0
-rw-r--r--app/assets/javascripts/work_items/graphql/typedefs.graphql38
-rw-r--r--app/assets/javascripts/work_items/graphql/widget.fragment.graphql3
-rw-r--r--app/assets/javascripts/work_items/graphql/work_item.query.graphql16
-rw-r--r--app/assets/javascripts/work_items/index.js4
-rw-r--r--app/assets/javascripts/work_items/pages/work_item_root.vue44
-rw-r--r--app/assets/javascripts/work_items/router/index.js14
-rw-r--r--app/assets/javascripts/work_items/router/routes.js8
-rw-r--r--app/controllers/application_controller.rb3
-rw-r--r--app/controllers/concerns/workhorse_authorization.rb2
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/concerns/ci/contextable.rb20
-rw-r--r--app/uploaders/bulk_imports/export_uploader.rb2
-rw-r--r--app/uploaders/import_export_uploader.rb4
-rw-r--r--app/views/projects/work_items/index.html.haml2
-rw-r--r--app/views/shared/_visibility_radios.html.haml1
-rw-r--r--config/feature_flags/development/ci_predefined_vars_in_builder.yml8
-rw-r--r--config/initializers/0_marginalia.rb5
-rw-r--r--config/routes/project.rb2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md1
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/api/integrations.md148
-rw-r--r--lib/api/api.rb3
-rw-r--r--lib/api/helpers/file_upload_helpers.rb2
-rw-r--r--lib/gitlab/ci/pipeline/metrics.rb9
-rw-r--r--lib/gitlab/ci/variables/builder.rb49
-rw-r--r--lib/gitlab/grape_logging/loggers/urgency_logger.rb19
-rw-r--r--lib/gitlab/health_checks/redis/cache_check.rb26
-rw-r--r--lib/gitlab/health_checks/redis/queues_check.rb26
-rw-r--r--lib/gitlab/health_checks/redis/rate_limiting_check.rb26
-rw-r--r--lib/gitlab/health_checks/redis/redis_abstract_check.rb41
-rw-r--r--lib/gitlab/health_checks/redis/redis_check.rb20
-rw-r--r--lib/gitlab/health_checks/redis/sessions_check.rb26
-rw-r--r--lib/gitlab/health_checks/redis/shared_state_check.rb26
-rw-r--r--lib/gitlab/health_checks/redis/trace_chunks_check.rb26
-rw-r--r--lib/gitlab/import_export/command_line_util.rb16
-rw-r--r--lib/gitlab/lograge/custom_options.rb17
-rw-r--r--lib/gitlab/metrics/rails_slis.rb7
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb14
-rw-r--r--qa/qa/resource/base.rb2
-rw-r--r--qa/qa/resource/project.rb4
-rw-r--r--qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb5
-rw-r--r--spec/controllers/application_controller_spec.rb28
-rw-r--r--spec/frontend/create_merge_request_dropdown_spec.js5
-rw-r--r--spec/frontend/diffs/components/app_spec.js19
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js39
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js10
-rw-r--r--spec/frontend/work_items/components/app_spec.js24
-rw-r--r--spec/frontend/work_items/mock_data.js17
-rw-r--r--spec/frontend/work_items/pages/work_item_root_spec.js70
-rw-r--r--spec/frontend/work_items/router_spec.js30
-rw-r--r--spec/lib/gitlab/ci/variables/builder_spec.rb38
-rw-r--r--spec/lib/gitlab/grape_logging/loggers/urgency_logger_spec.rb48
-rw-r--r--spec/lib/gitlab/health_checks/redis/redis_check_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb44
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb20
-rw-r--r--spec/lib/gitlab/metrics/rails_slis_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb62
-rw-r--r--spec/models/ci/build_spec.rb152
71 files changed, 1102 insertions, 425 deletions
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index 6b9d4feb3c8..6dd0997b3bb 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -14,6 +14,7 @@
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_ADMIN_USERNAME: "root"
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
+ GITLAB_QA_ADMIN_ACCESS_TOKEN: "${REVIEW_APPS_ROOT_TOKEN}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
EE_LICENSE: "${REVIEW_APPS_EE_LICENSE}"
SIGNUP_DISABLED: "true"
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index f4a27dc7d1f..ae6e6bf02e4 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -54,6 +54,7 @@ export default class CreateMergeRequestDropdown {
this.isCreatingBranch = false;
this.isCreatingMergeRequest = false;
this.isGettingRef = false;
+ this.refCancelToken = null;
this.mergeRequestCreated = false;
this.refDebounce = debounce((value, target) => this.getRef(value, target), 500);
this.refIsValid = true;
@@ -101,9 +102,18 @@ export default class CreateMergeRequestDropdown {
'click',
this.onClickCreateMergeRequestButton.bind(this),
);
+ this.branchInput.addEventListener('input', this.onChangeInput.bind(this));
this.branchInput.addEventListener('keyup', this.onChangeInput.bind(this));
this.dropdownToggle.addEventListener('click', this.onClickSetFocusOnBranchNameInput.bind(this));
+ // Detect for example when user pastes ref using the mouse
+ this.refInput.addEventListener('input', this.onChangeInput.bind(this));
+ // Detect for example when user presses right arrow to apply the suggested ref
this.refInput.addEventListener('keyup', this.onChangeInput.bind(this));
+ // Detect when user clicks inside the input to apply the suggested ref
+ this.refInput.addEventListener('click', this.onChangeInput.bind(this));
+ // Detect when user clicks outside the input to apply the suggested ref
+ this.refInput.addEventListener('blur', this.onChangeInput.bind(this));
+ // Detect when user presses tab to apply the suggested ref
this.refInput.addEventListener('keydown', CreateMergeRequestDropdown.processTab.bind(this));
}
@@ -247,8 +257,12 @@ export default class CreateMergeRequestDropdown {
getRef(ref, target = 'all') {
if (!ref) return false;
+ this.refCancelToken = axios.CancelToken.source();
+
return axios
- .get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`)
+ .get(`${createEndpoint(this.projectPath, this.refsPath)}${encodeURIComponent(ref)}`, {
+ cancelToken: this.refCancelToken.token,
+ })
.then(({ data }) => {
const branches = data[Object.keys(data)[0]];
const tags = data[Object.keys(data)[1]];
@@ -267,7 +281,10 @@ export default class CreateMergeRequestDropdown {
return this.updateInputState(target, ref, result);
})
- .catch(() => {
+ .catch((thrown) => {
+ if (axios.isCancel(thrown)) {
+ return false;
+ }
this.unavailable();
this.disable();
createFlash({
@@ -325,14 +342,23 @@ export default class CreateMergeRequestDropdown {
let target;
let value;
+ // User changed input, cancel to prevent previous request from interfering
+ if (this.refCancelToken !== null) {
+ this.refCancelToken.cancel();
+ }
+
if (event.target === this.branchInput) {
target = 'branch';
({ value } = this.branchInput);
} else if (event.target === this.refInput) {
target = 'ref';
- value =
- event.target.value.slice(0, event.target.selectionStart) +
- event.target.value.slice(event.target.selectionEnd);
+ if (event.target === document.activeElement) {
+ value =
+ event.target.value.slice(0, event.target.selectionStart) +
+ event.target.value.slice(event.target.selectionEnd);
+ } else {
+ value = event.target.value;
+ }
} else {
return false;
}
@@ -358,6 +384,7 @@ export default class CreateMergeRequestDropdown {
this.enable();
this.showAvailableMessage(target);
+ this.refDebounce(value, target);
return true;
}
@@ -414,7 +441,8 @@ export default class CreateMergeRequestDropdown {
if (!selectedText || this.refInput.dataset.value === this.suggestedRef) return;
event.preventDefault();
- window.getSelection().removeAllRanges();
+ const caretPositionEnd = this.refInput.value.length;
+ this.refInput.setSelectionRange(caretPositionEnd, caretPositionEnd);
}
removeMessage(target) {
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 465f9836140..cf0d2814136 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -392,8 +392,6 @@ export default {
diffsApp.instrument();
},
created() {
- this.mergeRequestContainers = document.querySelectorAll('.merge-request-container');
-
this.adjustView();
this.subscribeToEvents();
@@ -521,13 +519,6 @@ export default {
} else {
this.removeEventListeners();
}
-
- if (!this.isFluidLayout && this.glFeatures.mrChangesFluidLayout) {
- this.mergeRequestContainers.forEach((el) => {
- el.classList.toggle('limit-container-width', !this.shouldShow);
- el.classList.toggle('container-limited', !this.shouldShow);
- });
- }
},
setEventListeners() {
Mousetrap.bind(keysFor(MR_PREVIOUS_FILE_IN_DIFF), () => this.jumpToFile(-1));
diff --git a/app/assets/javascripts/pages/projects/work_items/index/index.js b/app/assets/javascripts/pages/projects/work_items/index.js
index 11c257611f0..11c257611f0 100644
--- a/app/assets/javascripts/pages/projects/work_items/index/index.js
+++ b/app/assets/javascripts/pages/projects/work_items/index.js
diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js
index e3868e2925d..1b57a69d464 100644
--- a/app/assets/javascripts/project_visibility.js
+++ b/app/assets/javascripts/project_visibility.js
@@ -1,42 +1,58 @@
import $ from 'jquery';
+import eventHub from '~/projects/new/event_hub';
-function setVisibilityOptions(namespaceSelector) {
- if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
- return;
- }
- const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
- const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset;
+// Values are from lib/gitlab/visibility_level.rb
+const visibilityLevel = {
+ private: 0,
+ internal: 10,
+ public: 20,
+};
+function setVisibilityOptions({ name, visibility, showPath, editPath }) {
document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => {
+ // Don't change anything if the option is restricted by admin
+ if (option.classList.contains('restricted')) {
+ return;
+ }
+
const optionInput = option.querySelector('input[type=radio]');
- const optionValue = optionInput ? optionInput.value : 0;
- const optionTitle = option.querySelector('.option-title');
- const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
+ const optionValue = optionInput ? parseInt(optionInput.value, 10) : 0;
- // don't change anything if the option is restricted by admin
- if (!option.classList.contains('restricted')) {
- if (visibilityLevel < optionValue) {
- option.classList.add('disabled');
- optionInput.disabled = true;
- const reason = option.querySelector('.option-disabled-reason');
- if (reason) {
- reason.innerHTML = `This project cannot be ${optionName} because the visibility of
+ if (visibilityLevel[visibility] < optionValue) {
+ option.classList.add('disabled');
+ optionInput.disabled = true;
+ const reason = option.querySelector('.option-disabled-reason');
+ if (reason) {
+ const optionTitle = option.querySelector('.option-title');
+ const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
+ reason.innerHTML = `This project cannot be ${optionName} because the visibility of
<a href="${showPath}">${name}</a> is ${visibility}. To make this project
${optionName}, you must first <a href="${editPath}">change the visibility</a>
of the parent group.`;
- }
- } else {
- option.classList.remove('disabled');
- optionInput.disabled = false;
}
+ } else {
+ option.classList.remove('disabled');
+ optionInput.disabled = false;
}
});
}
+function handleSelect2DropdownChange(namespaceSelector) {
+ if (!namespaceSelector || !('selectedIndex' in namespaceSelector)) {
+ return;
+ }
+ const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
+ setVisibilityOptions(selectedNamespace.dataset);
+}
+
export default function initProjectVisibilitySelector() {
+ eventHub.$on('update-visibility', setVisibilityOptions);
+
const namespaceSelector = document.querySelector('select.js-select-namespace');
if (namespaceSelector) {
- $('.select2.js-select-namespace').on('change', () => setVisibilityOptions(namespaceSelector));
- setVisibilityOptions(namespaceSelector);
+ $('.select2.js-select-namespace').on('change', () =>
+ handleSelect2DropdownChange(namespaceSelector),
+ );
+ handleSelect2DropdownChange(namespaceSelector);
}
}
diff --git a/app/assets/javascripts/projects/new/components/new_project_url_select.vue b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
index bf44ff70562..e0ba60074af 100644
--- a/app/assets/javascripts/projects/new/components/new_project_url_select.vue
+++ b/app/assets/javascripts/projects/new/components/new_project_url_select.vue
@@ -6,9 +6,9 @@ import {
GlDropdownItem,
GlDropdownText,
GlDropdownSectionHeader,
- GlLoadingIcon,
GlSearchBoxByType,
} from '@gitlab/ui';
+import { joinPaths } from '~/lib/utils/url_utility';
import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import Tracking from '~/tracking';
@@ -24,7 +24,6 @@ export default {
GlDropdownItem,
GlDropdownText,
GlDropdownSectionHeader,
- GlLoadingIcon,
GlSearchBoxByType,
},
mixins: [Tracking.mixin()],
@@ -103,6 +102,15 @@ export default {
focusInput() {
this.$refs.search.focusInput();
},
+ handleDropdownItemClick(namespace) {
+ eventHub.$emit('update-visibility', {
+ name: namespace.name,
+ visibility: namespace.visibility,
+ showPath: namespace.webUrl,
+ editPath: joinPaths(namespace.webUrl, '-', 'edit'),
+ });
+ this.setNamespace(namespace);
+ },
handleSelectTemplate(groupId) {
this.groupToFilterBy = this.userGroups.find(
(group) => getIdFromGraphQLId(group.id) === groupId,
@@ -134,23 +142,23 @@ export default {
<gl-search-box-by-type
ref="search"
v-model.trim="search"
+ :is-loading="$apollo.queries.currentUser.loading"
data-qa-selector="select_namespace_dropdown_search_field"
/>
- <gl-loading-icon v-if="$apollo.queries.currentUser.loading" />
- <template v-else>
+ <template v-if="!$apollo.queries.currentUser.loading">
<template v-if="hasGroupMatches">
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="group of filteredGroups"
:key="group.id"
- @click="setNamespace(group)"
+ @click="handleDropdownItemClick(group)"
>
{{ group.fullPath }}
</gl-dropdown-item>
</template>
<template v-if="hasNamespaceMatches">
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
- <gl-dropdown-item @click="setNamespace(userNamespace)">
+ <gl-dropdown-item @click="handleDropdownItemClick(userNamespace)">
{{ userNamespace.fullPath }}
</gl-dropdown-item>
</template>
@@ -158,6 +166,11 @@ export default {
</template>
</gl-dropdown>
- <input type="hidden" name="project[namespace_id]" :value="selectedNamespace.id" />
+ <input
+ id="project_namespace_id"
+ type="hidden"
+ name="project[namespace_id]"
+ :value="selectedNamespace.id"
+ />
</gl-button-group>
</template>
diff --git a/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql b/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql
index e16fe5dde49..74febec5a51 100644
--- a/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql
+++ b/app/assets/javascripts/projects/new/queries/search_namespaces_where_user_can_create_projects.query.graphql
@@ -4,6 +4,9 @@ query searchNamespacesWhereUserCanCreateProjects($search: String) {
nodes {
id
fullPath
+ name
+ visibility
+ webUrl
}
}
namespace {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
index 562ff154001..0ea22eb7aea 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js
@@ -69,8 +69,7 @@ export default {
if (isScopedLabel(candidateLabel)) {
const scopedKeyWithDelimiter = `${scopedLabelKey(candidateLabel)}${SCOPED_LABEL_DELIMITER}`;
const currentActiveScopedLabel = state.labels.find(
- ({ set, title }) =>
- set && title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
+ ({ title }) => title.startsWith(scopedKeyWithDelimiter) && title !== candidateLabel.title,
);
if (currentActiveScopedLabel) {
diff --git a/app/assets/javascripts/work_items/components/app.vue b/app/assets/javascripts/work_items/components/app.vue
index 93de17d1e43..a14d0c32cbe 100644
--- a/app/assets/javascripts/work_items/components/app.vue
+++ b/app/assets/javascripts/work_items/components/app.vue
@@ -1,9 +1,5 @@
-<script>
-export default {
- name: 'WorkItemRoot',
-};
-</script>
-
<template>
- <div></div>
+ <div>
+ <router-view />
+ </div>
</template>
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
new file mode 100644
index 00000000000..b39f68abf74
--- /dev/null
+++ b/app/assets/javascripts/work_items/constants.js
@@ -0,0 +1,3 @@
+export const widgetTypes = {
+ title: 'TITLE',
+};
diff --git a/app/assets/javascripts/work_items/graphql/fragmentTypes.json b/app/assets/javascripts/work_items/graphql/fragmentTypes.json
new file mode 100644
index 00000000000..c048ac34ac0
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/fragmentTypes.json
@@ -0,0 +1 @@
+{"__schema":{"types":[{"kind":"INTERFACE","name":"WorkItemWidget","possibleTypes":[{"name":"TitleWidget"}]}]}}
diff --git a/app/assets/javascripts/work_items/graphql/provider.js b/app/assets/javascripts/work_items/graphql/provider.js
new file mode 100644
index 00000000000..dae663433a6
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/provider.js
@@ -0,0 +1,54 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import createDefaultClient from '~/lib/graphql';
+import workItemQuery from './work_item.query.graphql';
+import introspectionQueryResultData from './fragmentTypes.json';
+
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
+
+export function createApolloProvider() {
+ Vue.use(VueApollo);
+
+ const defaultClient = createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ },
+ assumeImmutableResults: true,
+ },
+ );
+
+ defaultClient.cache.writeQuery({
+ query: workItemQuery,
+ variables: {
+ id: '1',
+ },
+ data: {
+ workItem: {
+ __typename: 'WorkItem',
+ id: '1',
+ type: 'FEATURE',
+ widgets: {
+ __typename: 'WorkItemWidgetConnection',
+ nodes: [
+ {
+ __typename: 'TitleWidget',
+ type: 'TITLE',
+ enabled: true,
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ contentText: 'Test',
+ },
+ ],
+ },
+ },
+ },
+ });
+
+ return new VueApollo({
+ defaultClient,
+ });
+}
diff --git a/app/assets/javascripts/work_items/graphql/resolvers.js b/app/assets/javascripts/work_items/graphql/resolvers.js
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/resolvers.js
diff --git a/app/assets/javascripts/work_items/graphql/typedefs.graphql b/app/assets/javascripts/work_items/graphql/typedefs.graphql
index e69de29bb2d..4a6e4aeed60 100644
--- a/app/assets/javascripts/work_items/graphql/typedefs.graphql
+++ b/app/assets/javascripts/work_items/graphql/typedefs.graphql
@@ -0,0 +1,38 @@
+enum WorkItemType {
+ FEATURE
+}
+
+enum WidgetType {
+ TITLE
+}
+
+interface WorkItemWidget {
+ type: WidgetType!
+}
+
+# Replicating Relay connection type for client schema
+type WorkItemWidgetEdge {
+ cursor: String!
+ node: WorkItemWidget
+}
+
+type WorkItemWidgetConnection {
+ edges: [WorkItemWidgetEdge]
+ nodes: [WorkItemWidget]
+ pageInfo: PageInfo!
+}
+
+type TitleWidget implements WorkItemWidget {
+ type: WidgetType!
+ contentText: String!
+}
+
+type WorkItem {
+ id: ID!
+ type: WorkItemType!
+ widgets: [WorkItemWidgetConnection]
+}
+
+extend type Query {
+ workItem(id: ID!): WorkItem!
+}
diff --git a/app/assets/javascripts/work_items/graphql/widget.fragment.graphql b/app/assets/javascripts/work_items/graphql/widget.fragment.graphql
new file mode 100644
index 00000000000..d7608c26052
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/widget.fragment.graphql
@@ -0,0 +1,3 @@
+fragment WidgetBase on WorkItemWidget {
+ type
+}
diff --git a/app/assets/javascripts/work_items/graphql/work_item.query.graphql b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
new file mode 100644
index 00000000000..549e4f8c65a
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/work_item.query.graphql
@@ -0,0 +1,16 @@
+#import './widget.fragment.graphql'
+
+query WorkItem($id: ID!) {
+ workItem(id: $id) @client {
+ id
+ type
+ widgets {
+ nodes {
+ ...WidgetBase
+ ... on TitleWidget {
+ contentText
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js
index a635d43776d..7cc8a23b7b1 100644
--- a/app/assets/javascripts/work_items/index.js
+++ b/app/assets/javascripts/work_items/index.js
@@ -1,11 +1,15 @@
import Vue from 'vue';
import App from './components/app.vue';
+import { createRouter } from './router';
+import { createApolloProvider } from './graphql/provider';
export const initWorkItemsRoot = () => {
const el = document.querySelector('#js-work-items');
return new Vue({
el,
+ router: createRouter(el.dataset.fullPath),
+ apolloProvider: createApolloProvider(),
render(createElement) {
return createElement(App);
},
diff --git a/app/assets/javascripts/work_items/pages/work_item_root.vue b/app/assets/javascripts/work_items/pages/work_item_root.vue
new file mode 100644
index 00000000000..e2ae15e0c7c
--- /dev/null
+++ b/app/assets/javascripts/work_items/pages/work_item_root.vue
@@ -0,0 +1,44 @@
+<script>
+import workItemQuery from '../graphql/work_item.query.graphql';
+import { widgetTypes } from '../constants';
+
+export default {
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ workItem: null,
+ };
+ },
+ apollo: {
+ workItem: {
+ query: workItemQuery,
+ variables() {
+ return {
+ id: this.id,
+ };
+ },
+ },
+ },
+ computed: {
+ titleWidgetData() {
+ return this.workItem?.widgets?.nodes?.find((widget) => widget.type === widgetTypes.title);
+ },
+ },
+};
+</script>
+
+<template>
+ <section>
+ <!-- Title widget placeholder -->
+ <div>
+ <h2 v-if="titleWidgetData" class="title" data-testid="title">
+ {{ titleWidgetData.contentText }}
+ </h2>
+ </div>
+ </section>
+</template>
diff --git a/app/assets/javascripts/work_items/router/index.js b/app/assets/javascripts/work_items/router/index.js
new file mode 100644
index 00000000000..142fab8cfa6
--- /dev/null
+++ b/app/assets/javascripts/work_items/router/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { routes } from './routes';
+
+Vue.use(VueRouter);
+
+export function createRouter(fullPath) {
+ return new VueRouter({
+ routes,
+ mode: 'history',
+ base: joinPaths(fullPath, '-', 'work_items'),
+ });
+}
diff --git a/app/assets/javascripts/work_items/router/routes.js b/app/assets/javascripts/work_items/router/routes.js
new file mode 100644
index 00000000000..a3cf44ad4ca
--- /dev/null
+++ b/app/assets/javascripts/work_items/router/routes.js
@@ -0,0 +1,8 @@
+export const routes = [
+ {
+ path: '/:id',
+ name: 'work_item',
+ component: () => import('../pages/work_item_root.vue'),
+ props: true,
+ },
+];
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b22167a3952..3af1afab06e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -163,7 +163,8 @@ class ApplicationController < ActionController::Base
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
payload[:metadata] = @current_context
-
+ payload[:request_urgency] = urgency&.name
+ payload[:target_duration_s] = urgency&.duration
logged_user = auth_user
if logged_user.present?
payload[:user_id] = logged_user.try(:id)
diff --git a/app/controllers/concerns/workhorse_authorization.rb b/app/controllers/concerns/workhorse_authorization.rb
index a290ba256b6..648e6f409e6 100644
--- a/app/controllers/concerns/workhorse_authorization.rb
+++ b/app/controllers/concerns/workhorse_authorization.rb
@@ -38,6 +38,6 @@ module WorkhorseAuthorization
end
def file_extension_whitelist
- ImportExportUploader::EXTENSION_WHITELIST
+ ImportExportUploader::EXTENSION_ALLOWLIST
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0041ec5135c..ef25ced8610 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -780,6 +780,10 @@ module Ci
strong_memoize(:legacy_trigger) { trigger_requests.first }
end
+ def variables_builder
+ @variables_builder ||= ::Gitlab::Ci::Variables::Builder.new(self)
+ end
+
def persisted_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless persisted?
@@ -1254,6 +1258,12 @@ module Ci
self.builds.latest.build_matchers(project)
end
+ def predefined_vars_in_builder_enabled?
+ strong_memoize(:predefined_vars_in_builder_enabled) do
+ Feature.enabled?(:ci_predefined_vars_in_builder, project, default_enabled: :yaml)
+ end
+ end
+
private
def add_message(severity, content)
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
index 27a704c1de0..6871482f71d 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -10,8 +10,10 @@ module Ci
# Variables in the environment name scope.
#
def scoped_variables(environment: expanded_environment_name, dependencies: true)
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables.concat(predefined_variables)
+ track_duration do
+ variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
+
+ variables.concat(predefined_variables) unless pipeline.predefined_vars_in_builder_enabled?
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(runner.predefined_variables) if runnable? && runner
@@ -25,9 +27,23 @@ module Ci
variables.concat(trigger_request.user_variables) if trigger_request
variables.concat(pipeline.variables)
variables.concat(pipeline.pipeline_schedule.job_variables) if pipeline.pipeline_schedule
+
+ variables
end
end
+ def track_duration
+ start_time = ::Gitlab::Metrics::System.monotonic_time
+ result = yield
+ duration = ::Gitlab::Metrics::System.monotonic_time - start_time
+
+ ::Gitlab::Ci::Pipeline::Metrics
+ .pipeline_builder_scoped_variables_histogram
+ .observe({}, duration.seconds)
+
+ result
+ end
+
##
# Variables that do not depend on the environment name.
#
diff --git a/app/uploaders/bulk_imports/export_uploader.rb b/app/uploaders/bulk_imports/export_uploader.rb
index 356e5ce028e..cd6e599054b 100644
--- a/app/uploaders/bulk_imports/export_uploader.rb
+++ b/app/uploaders/bulk_imports/export_uploader.rb
@@ -2,6 +2,6 @@
module BulkImports
class ExportUploader < ImportExportUploader
- EXTENSION_WHITELIST = %w[ndjson.gz].freeze
+ EXTENSION_ALLOWLIST = %w[ndjson.gz].freeze
end
end
diff --git a/app/uploaders/import_export_uploader.rb b/app/uploaders/import_export_uploader.rb
index 369afba2bae..7b161d72efb 100644
--- a/app/uploaders/import_export_uploader.rb
+++ b/app/uploaders/import_export_uploader.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
class ImportExportUploader < AttachmentUploader
- EXTENSION_WHITELIST = %w[tar.gz gz].freeze
+ EXTENSION_ALLOWLIST = %w[tar.gz gz].freeze
def self.workhorse_local_upload_path
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
end
def extension_whitelist
- EXTENSION_WHITELIST
+ EXTENSION_ALLOWLIST
end
def move_to_cache
diff --git a/app/views/projects/work_items/index.html.haml b/app/views/projects/work_items/index.html.haml
index 052db598571..0efd7a740d3 100644
--- a/app/views/projects/work_items/index.html.haml
+++ b/app/views/projects/work_items/index.html.haml
@@ -1,3 +1,3 @@
- page_title s_('WorkItem|Work Items')
-#js-work-items
+#js-work-items{ data: { full_path: @project.full_path } }
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index f48bfcd0e72..760fe18ddec 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -10,6 +10,7 @@
= visibility_level_label(level)
.option-description
= visibility_level_description(level, form_model)
+ .option-disabled-reason
.text-muted
- if all_visibility_levels_restricted?
diff --git a/config/feature_flags/development/ci_predefined_vars_in_builder.yml b/config/feature_flags/development/ci_predefined_vars_in_builder.yml
new file mode 100644
index 00000000000..5aacf6ee681
--- /dev/null
+++ b/config/feature_flags/development/ci_predefined_vars_in_builder.yml
@@ -0,0 +1,8 @@
+---
+name: ci_predefined_vars_in_builder
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72348
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/231300
+milestone: '14.4'
+type: development
+group: group::pipeline authoring
+default_enabled: false
diff --git a/config/initializers/0_marginalia.rb b/config/initializers/0_marginalia.rb
index f7a1f5f0469..805a9e33347 100644
--- a/config/initializers/0_marginalia.rb
+++ b/config/initializers/0_marginalia.rb
@@ -19,7 +19,10 @@ Marginalia::Comment.components = [:application, :correlation_id, :jid, :endpoint
# adding :line has some overhead because a regexp on the backtrace has
# to be run on every SQL query. Only enable this in development because
# we've seen it slow things down.
-Marginalia::Comment.components << :line if Rails.env.development?
+if Rails.env.development?
+ Marginalia::Comment.components << :line
+ Marginalia::Comment.lines_to_ignore = Regexp.union(Gitlab::BacktraceCleaner::IGNORE_BACKTRACES + %w(lib/ruby/gems/ lib/gem_extensions/ lib/ruby/))
+end
Gitlab::Marginalia.set_application_name
diff --git a/config/routes/project.rb b/config/routes/project.rb
index b1be9ad2ada..496d63785e6 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -358,7 +358,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'details', on: :member
end
- resources :work_items, only: [:index]
+ get 'work_items/*work_items_path' => 'work_items#index', as: :work_items
resource :tracing, only: [:show]
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 934ec8d6a83..78e3314b189 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -41,6 +41,7 @@ The following metrics are available:
| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` |
| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | |
| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation` |
+| `gitlab_ci_pipeline_builder_scoped_variables_duration` | Histogram | 14.5 | Time in seconds it takes to create the scoped variables for a CI/CD job
| `gitlab_ci_pipeline_creation_duration_seconds` | Histogram | 13.0 | Time in seconds it takes to create a CI/CD pipeline | |
| `gitlab_ci_pipeline_size_builds` | Histogram | 13.1 | Total number of builds within a pipeline grouped by a pipeline source | `source` |
| `job_waiter_started_total` | Counter | 12.9 | Number of batches of jobs started where a web request is waiting for the jobs to complete | `worker` |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 277a34ea667..dc2d53a0e05 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -4042,6 +4042,7 @@ Input type: `ScanExecutionPolicyCommitInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationscanexecutionpolicycommitclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationscanexecutionpolicycommitname"></a>`name` | [`String`](#string) | Name of the policy. If the name is null, the `name` field from `policy_yaml` is used. |
| <a id="mutationscanexecutionpolicycommitoperationmode"></a>`operationMode` | [`MutationOperationMode!`](#mutationoperationmode) | Changes the operation mode. |
| <a id="mutationscanexecutionpolicycommitpolicyyaml"></a>`policyYaml` | [`String!`](#string) | YAML snippet of the policy. |
| <a id="mutationscanexecutionpolicycommitprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
diff --git a/doc/api/integrations.md b/doc/api/integrations.md
index 3c649e8d044..64154971d03 100644
--- a/doc/api/integrations.md
+++ b/doc/api/integrations.md
@@ -94,9 +94,9 @@ Parameters:
| `restrict_to_branch` | string | false | Comma-separated list of branches to be are automatically inspected. Leave blank to include all branches. |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Asana integration
+### Disable Asana integration
-Delete Asana integration for a project.
+Disable the Asana integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/asana
@@ -130,9 +130,9 @@ Parameters:
| `subdomain` | string | false | The subdomain setting |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Assembla integration
+### Disable Assembla integration
-Delete Assembla integration for a project.
+Disable the Assembla integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/assembla
@@ -170,9 +170,9 @@ Parameters:
| `password` | string | true | Password of the user |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Atlassian Bamboo CI integration
+### Disable Atlassian Bamboo CI integration
-Delete Atlassian Bamboo CI integration for a project.
+Disable the Atlassian Bamboo CI integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/bamboo
@@ -209,9 +209,9 @@ Parameters:
| `title` | string | false | Title |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Bugzilla integration
+### Disable Bugzilla integration
-Delete Bugzilla integration for a project.
+Disable the Bugzilla integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/bugzilla
@@ -246,9 +246,9 @@ Parameters:
| `enable_ssl_verification` | boolean | false | DEPRECATED: This parameter has no effect since SSL verification is always enabled |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Buildkite integration
+### Disable Buildkite integration
-Delete Buildkite integration for a project.
+Disable the Buildkite integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/buildkite
@@ -284,9 +284,9 @@ Parameters:
| `room` | string | false | Campfire room. The last part of the URL when you're in a room. |
| `push_events` | boolean | false | Enable notifications for push events. |
-### Delete Campfire integration
+### Disable Campfire integration
-Delete Campfire integration for a project.
+Disable the Campfire integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/campfire
@@ -322,9 +322,9 @@ Parameters:
| `datadog_service` | string | false | Name of this GitLab instance that all data will be tagged with |
| `datadog_env` | string | false | The environment tag that traces will be tagged with |
-### Delete Datadog integration
+### Disable Datadog integration
-Delete Datadog integration for a project.
+Disable the Datadog integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/datadog
@@ -367,9 +367,9 @@ Parameters:
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
-### Delete Unify Circuit integration
+### Disable Unify Circuit integration
-Delete Unify Circuit integration for a project.
+Disable the Unify Circuit integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/unify-circuit
@@ -412,9 +412,9 @@ Parameters:
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
-### Delete Webex Teams integration
+### Disable Webex Teams integration
-Delete Webex Teams integration for a project.
+Disable the Webex Teams integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/webex-teams
@@ -451,9 +451,9 @@ Parameters:
| `title` | string | false | Title |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Custom Issue Tracker integration
+### Disable Custom Issue Tracker integration
-Delete Custom Issue Tracker integration for a project.
+Disable the Custom Issue Tracker integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/custom-issue-tracker
@@ -485,9 +485,9 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `webhook` | string | true | Discord webhook. For example, `https://discord.com/api/webhooks/…` |
-### Delete Discord integration
+### Disable Discord integration
-Delete Discord integration for a project.
+Disable the Discord integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/discord
@@ -524,9 +524,9 @@ Parameters:
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
-### Delete Drone CI integration
+### Disable Drone CI integration
-Delete Drone CI integration for a project.
+Disable the Drone CI integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/drone-ci
@@ -563,9 +563,9 @@ Parameters:
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
| `branches_to_be_notified` | string | false | Branches to send notifications for. Valid options are "all", "default", "protected", and "default_and_protected". Notifications are always fired for tag pushes. The default value is "all" |
-### Delete Emails on Push integration
+### Disable Emails on Push integration
-Delete Emails on Push integration for a project.
+Disable the Emails on Push integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/emails-on-push
@@ -599,9 +599,9 @@ Parameters:
| `project_url` | string | true | The URL to the project in EWM |
| `issues_url` | string | true | The URL to view an issue in EWM. Must contain `:id` |
-### Delete EWM integration
+### Disable EWM integration
-Delete EWM integration for a project.
+Disable the EWM integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/ewm
@@ -635,9 +635,9 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `confluence_url` | string | true | The URL of the Confluence Cloud Workspace hosted on atlassian.net. |
-### Delete Confluence integration
+### Disable Confluence integration
-Delete Confluence integration for a project.
+Disable the Confluence integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/confluence
@@ -669,9 +669,9 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `external_wiki_url` | string | true | The URL of the external wiki |
-### Delete External wiki integration
+### Disable External wiki integration
-Delete External wiki integration for a project.
+Disable the External wiki integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/external-wiki
@@ -706,9 +706,9 @@ Parameters:
| `token` | string | true | Flowdock Git source token |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Flowdock integration
+### Disable Flowdock integration
-Delete Flowdock integration for a project.
+Disable the Flowdock integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/flowdock
@@ -742,9 +742,9 @@ Parameters:
| `repository_url` | string | true | GitHub repository URL |
| `static_context` | boolean | false | Append instance name instead of branch to [status check name](../user/project/integrations/github.md#static--dynamic-status-check-names) |
-### Delete GitHub integration
+### Disable GitHub integration
-Delete GitHub integration for a project.
+Disable the GitHub integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/github
@@ -788,9 +788,9 @@ Parameters:
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
-### Delete Hangouts Chat integration
+### Disable Hangouts Chat integration
-Delete Hangouts Chat integration for a project.
+Disable the Hangouts Chat integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/hangouts-chat
@@ -829,9 +829,9 @@ Parameters:
| `colorize_messages` | boolean | false | Colorize messages |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Irker (IRC gateway) integration
+### Disable Irker (IRC gateway) integration
-Delete Irker (IRC gateway) integration for a project.
+Disable the Irker (IRC gateway) integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/irker
@@ -880,9 +880,9 @@ Parameters:
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
| `comment_on_event_enabled` | boolean | false | Enable comments inside Jira issues on each GitLab event (commit / merge request) |
-### Delete Jira integration
+### Disable Jira integration
-Remove all previously Jira integrations from a project.
+Disable the Jira integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/jira
@@ -939,9 +939,9 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Slack token |
-### Delete Slack Slash Command integration
+### Disable Slack Slash Command integration
-Delete Slack Slash Command integration for a project.
+Disable the Slack Slash Command integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/slack-slash-commands
@@ -974,9 +974,9 @@ Parameters:
| `token` | string | yes | The Mattermost token |
| `username` | string | no | The username to use to post the message |
-### Delete Mattermost Slash Command integration
+### Disable Mattermost Slash Command integration
-Delete Mattermost Slash Command integration for a project.
+Disable the Mattermost Slash Command integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/mattermost-slash-commands
@@ -1005,9 +1005,9 @@ Parameters:
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
| `tag_push_events` | boolean | false | Enable notifications for tag push events |
-### Delete Packagist integration
+### Disable Packagist integration
-Delete Packagist integration for a project.
+Disable the Packagist integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/packagist
@@ -1044,9 +1044,9 @@ Parameters:
| `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/28271)) |
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
-### Delete Pipeline-Emails integration
+### Disable Pipeline-Emails integration
-Delete Pipeline-Emails integration for a project.
+Disable the Pipeline-Emails integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/pipelines-email
@@ -1082,9 +1082,9 @@ Parameters:
| `restrict_to_branch` | boolean | false | Comma-separated list of branches to automatically inspect. Leave blank to include all branches. |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Pivotal Tracker integration
+### Disable Pivotal Tracker integration
-Delete Pivotal Tracker integration for a project.
+Disable the Pivotal Tracker integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/pivotaltracker
@@ -1118,9 +1118,9 @@ Parameters:
| `google_iap_audience_client_id` | string | false | Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com) |
| `google_iap_service_account_json` | string | false | `credentials.json` file for your service account, like { "type": "service_account", "project_id": ... } |
-### Delete Prometheus integration
+### Disable Prometheus integration
-Delete Prometheus integration for a project.
+Disable the Prometheus integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/prometheus
@@ -1157,9 +1157,9 @@ Parameters:
| `sound` | string | false | The sound of the notification |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Pushover integration
+### Disable Pushover integration
-Delete Pushover integration for a project.
+Disable the Pushover integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/pushover
@@ -1195,9 +1195,9 @@ Parameters:
| `description` | string | false | Description |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete Redmine integration
+### Disable Redmine integration
-Delete Redmine integration for a project.
+Disable the Redmine integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/redmine
@@ -1256,9 +1256,9 @@ Parameters:
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
-### Delete Slack integration
+### Disable Slack integration
-Delete Slack integration for a project.
+Disable the Slack integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/slack
@@ -1302,9 +1302,9 @@ Parameters:
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
-### Delete Microsoft Teams integration
+### Disable Microsoft Teams integration
-Delete Microsoft Teams integration for a project.
+Disable the Microsoft Teams integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/microsoft-teams
@@ -1359,9 +1359,9 @@ Parameters:
| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
-### Delete Mattermost notifications integration
+### Disable Mattermost notifications integration
-Delete Mattermost notifications integration for a project.
+Disable the Mattermost notifications integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/mattermost
@@ -1399,9 +1399,9 @@ Parameters:
| `password` | string | true | The password of the user |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete JetBrains TeamCity CI integration
+### Disable JetBrains TeamCity CI integration
-Delete JetBrains TeamCity CI integration for a project.
+Disable the JetBrains TeamCity CI integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/teamcity
@@ -1439,9 +1439,9 @@ Parameters:
| `merge_requests_events` | boolean | false | Enable notifications for merge request events. |
| `tag_push_events` | boolean | false | Enable notifications for tag push events. |
-### Delete Jenkins CI integration
+### Disable Jenkins CI integration
-Delete Jenkins CI integration for a project.
+Disable the Jenkins CI integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/jenkins
@@ -1476,9 +1476,9 @@ Parameters:
- `multiproject_enabled` (optional) - Multi-project mode is configured in Jenkins GitLab Hook plugin
- `pass_unstable` (optional) - Unstable builds are treated as passing
-### Delete Jenkins CI (Deprecated) integration
+### Disable Jenkins CI (Deprecated) integration
-Delete Jenkins CI (Deprecated) integration for a project.
+Disable the Jenkins CI (Deprecated) integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/jenkins-deprecated
@@ -1512,9 +1512,9 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `mock_service_url` | string | true | `http://localhost:4004` |
-### Delete MockCI integration
+### Disable MockCI integration
-Delete MockCI integration for a project.
+Disable the MockCI integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/mock-ci
@@ -1549,9 +1549,9 @@ Parameters:
| `description` | string | false | Description |
| `push_events` | boolean | false | Enable notifications for push events |
-### Delete YouTrack integration
+### Disable YouTrack integration
-Delete YouTrack integration for a project.
+Disable the YouTrack integration for a project. Integration settings are preserved.
```plaintext
DELETE /projects/:id/integrations/youtrack
diff --git a/lib/api/api.rb b/lib/api/api.rb
index a4d42c735cb..0d5cf2792af 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -27,7 +27,8 @@ module API
Gitlab::GrapeLogging::Loggers::PerfLogger.new,
Gitlab::GrapeLogging::Loggers::CorrelationIdLogger.new,
Gitlab::GrapeLogging::Loggers::ContextLogger.new,
- Gitlab::GrapeLogging::Loggers::ContentLogger.new
+ Gitlab::GrapeLogging::Loggers::ContentLogger.new,
+ Gitlab::GrapeLogging::Loggers::UrgencyLogger.new
]
allow_access_with_scope :api
diff --git a/lib/api/helpers/file_upload_helpers.rb b/lib/api/helpers/file_upload_helpers.rb
index dd551ec2976..751972b44f0 100644
--- a/lib/api/helpers/file_upload_helpers.rb
+++ b/lib/api/helpers/file_upload_helpers.rb
@@ -5,7 +5,7 @@ module API
module FileUploadHelpers
def file_is_valid?
filename = params[:file]&.original_filename
- filename && ImportExportUploader::EXTENSION_WHITELIST.include?(File.extname(filename).delete('.'))
+ filename && ImportExportUploader::EXTENSION_ALLOWLIST.include?(File.extname(filename).delete('.'))
end
def validate_file!
diff --git a/lib/gitlab/ci/pipeline/metrics.rb b/lib/gitlab/ci/pipeline/metrics.rb
index 321efa7854f..b5e48f210ad 100644
--- a/lib/gitlab/ci/pipeline/metrics.rb
+++ b/lib/gitlab/ci/pipeline/metrics.rb
@@ -51,6 +51,15 @@ module Gitlab
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
end
+ def self.pipeline_builder_scoped_variables_histogram
+ name = :gitlab_ci_pipeline_builder_scoped_variables_duration
+ comment = 'Pipeline variables builder scoped_variables duration'
+ labels = {}
+ buckets = [0.01, 0.05, 0.1, 0.3, 0.5, 1, 2, 5, 10, 30, 60, 120]
+
+ ::Gitlab::Metrics.histogram(name, comment, labels, buckets)
+ end
+
def self.pipeline_processing_events_counter
name = :gitlab_ci_pipeline_processing_events_total
comment = 'Total amount of pipeline processing events'
diff --git a/lib/gitlab/ci/variables/builder.rb b/lib/gitlab/ci/variables/builder.rb
new file mode 100644
index 00000000000..f4c5a06af97
--- /dev/null
+++ b/lib/gitlab/ci/variables/builder.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Variables
+ class Builder
+ include ::Gitlab::Utils::StrongMemoize
+
+ def initialize(pipeline)
+ @pipeline = pipeline
+ end
+
+ def scoped_variables(job, environment:, dependencies:)
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.concat(predefined_variables(job)) if pipeline.predefined_vars_in_builder_enabled?
+ end
+ end
+
+ private
+
+ attr_reader :pipeline
+
+ def predefined_variables(job)
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables.append(key: 'CI_JOB_NAME', value: job.name)
+ variables.append(key: 'CI_JOB_STAGE', value: job.stage)
+ variables.append(key: 'CI_JOB_MANUAL', value: 'true') if job.action?
+ variables.append(key: 'CI_PIPELINE_TRIGGERED', value: 'true') if job.trigger_request
+
+ variables.append(key: 'CI_NODE_INDEX', value: job.options[:instance].to_s) if job.options&.include?(:instance)
+ variables.append(key: 'CI_NODE_TOTAL', value: ci_node_total_value(job).to_s)
+
+ # legacy variables
+ variables.append(key: 'CI_BUILD_NAME', value: job.name)
+ variables.append(key: 'CI_BUILD_STAGE', value: job.stage)
+ variables.append(key: 'CI_BUILD_TRIGGERED', value: 'true') if job.trigger_request
+ variables.append(key: 'CI_BUILD_MANUAL', value: 'true') if job.action?
+ end
+ end
+
+ def ci_node_total_value(job)
+ parallel = job.options&.dig(:parallel)
+ parallel = parallel.dig(:total) if parallel.is_a?(Hash)
+ parallel || 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/grape_logging/loggers/urgency_logger.rb b/lib/gitlab/grape_logging/loggers/urgency_logger.rb
new file mode 100644
index 00000000000..0a503086d05
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/urgency_logger.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class UrgencyLogger < ::GrapeLogging::Loggers::Base
+ def parameters(request, _)
+ endpoint = request.env['api.endpoint']
+ return {} unless endpoint
+
+ urgency = endpoint.options[:for].try(:urgency_for_app, endpoint)
+ return {} unless urgency
+
+ { request_urgency: urgency.name, target_duration_s: urgency.duration }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis/cache_check.rb b/lib/gitlab/health_checks/redis/cache_check.rb
index 0c8fe83893b..bd843bdaac4 100644
--- a/lib/gitlab/health_checks/redis/cache_check.rb
+++ b/lib/gitlab/health_checks/redis/cache_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class CacheCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_cache_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::Cache.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/health_checks/redis/queues_check.rb b/lib/gitlab/health_checks/redis/queues_check.rb
index b1e33b9f459..fb92db937dc 100644
--- a/lib/gitlab/health_checks/redis/queues_check.rb
+++ b/lib/gitlab/health_checks/redis/queues_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class QueuesCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_queues_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::Queues.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/health_checks/redis/rate_limiting_check.rb b/lib/gitlab/health_checks/redis/rate_limiting_check.rb
index 67c14e26361..0e9d94f7dff 100644
--- a/lib/gitlab/health_checks/redis/rate_limiting_check.rb
+++ b/lib/gitlab/health_checks/redis/rate_limiting_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class RateLimitingCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_rate_limiting_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::RateLimiting.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/health_checks/redis/redis_abstract_check.rb b/lib/gitlab/health_checks/redis/redis_abstract_check.rb
new file mode 100644
index 00000000000..ecad4b06ea9
--- /dev/null
+++ b/lib/gitlab/health_checks/redis/redis_abstract_check.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module HealthChecks
+ module Redis
+ module RedisAbstractCheck
+ include SimpleAbstractCheck
+
+ def check_up
+ successful?(check)
+ end
+
+ private
+
+ def redis_instance_class_name
+ Gitlab::Redis.const_get(redis_instance_name.camelize, false)
+ end
+
+ def metric_prefix
+ "redis_#{redis_instance_name}_ping"
+ end
+
+ def redis_instance_name
+ name.sub(/_check$/, '')
+ end
+
+ def successful?(result)
+ result == 'PONG'
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def check
+ catch_timeout 10.seconds do
+ redis_instance_class_name.with(&:ping)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/health_checks/redis/redis_check.rb b/lib/gitlab/health_checks/redis/redis_check.rb
index 25879c18f84..c793a939abd 100644
--- a/lib/gitlab/health_checks/redis/redis_check.rb
+++ b/lib/gitlab/health_checks/redis/redis_check.rb
@@ -14,16 +14,22 @@ module Gitlab
end
def successful?(result)
- result == 'PONG'
+ result == true
end
def check
- ::Gitlab::HealthChecks::Redis::CacheCheck.check_up &&
- ::Gitlab::HealthChecks::Redis::QueuesCheck.check_up &&
- ::Gitlab::HealthChecks::Redis::SharedStateCheck.check_up &&
- ::Gitlab::HealthChecks::Redis::TraceChunksCheck.check_up &&
- ::Gitlab::HealthChecks::Redis::RateLimitingCheck.check_up &&
- ::Gitlab::HealthChecks::Redis::SessionsCheck.check_up
+ redis_health_checks.all?(&:check_up)
+ end
+
+ def redis_health_checks
+ [
+ Gitlab::HealthChecks::Redis::CacheCheck,
+ Gitlab::HealthChecks::Redis::QueuesCheck,
+ Gitlab::HealthChecks::Redis::SharedStateCheck,
+ Gitlab::HealthChecks::Redis::TraceChunksCheck,
+ Gitlab::HealthChecks::Redis::RateLimitingCheck,
+ Gitlab::HealthChecks::Redis::SessionsCheck
+ ]
end
end
end
diff --git a/lib/gitlab/health_checks/redis/sessions_check.rb b/lib/gitlab/health_checks/redis/sessions_check.rb
index a0c5e177b4e..90a4c868f40 100644
--- a/lib/gitlab/health_checks/redis/sessions_check.rb
+++ b/lib/gitlab/health_checks/redis/sessions_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class SessionsCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_sessions_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::Sessions.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/health_checks/redis/shared_state_check.rb b/lib/gitlab/health_checks/redis/shared_state_check.rb
index 285ac271929..80f91784b8c 100644
--- a/lib/gitlab/health_checks/redis/shared_state_check.rb
+++ b/lib/gitlab/health_checks/redis/shared_state_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class SharedStateCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_shared_state_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::SharedState.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/health_checks/redis/trace_chunks_check.rb b/lib/gitlab/health_checks/redis/trace_chunks_check.rb
index cf9fa700b0a..9a89a1ce51d 100644
--- a/lib/gitlab/health_checks/redis/trace_chunks_check.rb
+++ b/lib/gitlab/health_checks/redis/trace_chunks_check.rb
@@ -4,31 +4,7 @@ module Gitlab
module HealthChecks
module Redis
class TraceChunksCheck
- extend SimpleAbstractCheck
-
- class << self
- def check_up
- check
- end
-
- private
-
- def metric_prefix
- 'redis_trace_chunks_ping'
- end
-
- def successful?(result)
- result == 'PONG'
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def check
- catch_timeout 10.seconds do
- Gitlab::Redis::TraceChunks.with(&:ping)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ extend RedisAbstractCheck
end
end
end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index fdc4c22001f..6d3b92afb53 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -56,10 +56,20 @@ module Gitlab
end
def download(url, upload_path)
- File.open(upload_path, 'w') do |file|
- # Download (stream) file from the uploader's location
- IO.copy_stream(URI.parse(url).open, file)
+ File.open(upload_path, 'wb') do |file|
+ Gitlab::HTTP.get(url, stream_body: true) do |fragment|
+ if [301, 302, 307].include?(fragment.code)
+ Gitlab::Import::Logger.warn(message: "received redirect fragment", fragment_code: fragment.code)
+ elsif fragment.code == 200
+ file.write(fragment)
+ else
+ raise Gitlab::ImportExport::Error, "unsupported response downloading fragment #{fragment.code}"
+ end
+ end
end
+ rescue StandardError => e
+ @shared.error(e) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ raise e
end
def tar_with_options(archive:, dir:, options:)
diff --git a/lib/gitlab/lograge/custom_options.rb b/lib/gitlab/lograge/custom_options.rb
index 83fd74310d0..e6c9ba0773c 100644
--- a/lib/gitlab/lograge/custom_options.rb
+++ b/lib/gitlab/lograge/custom_options.rb
@@ -7,6 +7,8 @@ module Gitlab
LIMITED_ARRAY_SENTINEL = { key: 'truncated', value: '...' }.freeze
IGNORE_PARAMS = Set.new(%w(controller action format)).freeze
+ KNOWN_PAYLOAD_PARAMS = [:remote_ip, :user_id, :username, :ua, :queue_duration_s,
+ :etag_route, :request_urgency, :target_duration_s] + CLOUDFLARE_CUSTOM_HEADERS.values
def self.call(event)
params = event
@@ -14,24 +16,17 @@ module Gitlab
.each_with_object([]) { |(k, v), array| array << { key: k, value: v } unless IGNORE_PARAMS.include?(k) }
payload = {
time: Time.now.utc.iso8601(3),
- params: Gitlab::Utils::LogLimitedArray.log_limited_array(params, sentinel: LIMITED_ARRAY_SENTINEL),
- remote_ip: event.payload[:remote_ip],
- user_id: event.payload[:user_id],
- username: event.payload[:username],
- ua: event.payload[:ua]
+ params: Gitlab::Utils::LogLimitedArray.log_limited_array(params, sentinel: LIMITED_ARRAY_SENTINEL)
}
+
payload.merge!(event.payload[:metadata]) if event.payload[:metadata]
+ optional_payload_params = event.payload.slice(*KNOWN_PAYLOAD_PARAMS).compact
+ payload.merge!(optional_payload_params)
::Gitlab::InstrumentationHelper.add_instrumentation_data(payload)
- payload[:queue_duration_s] = event.payload[:queue_duration_s] if event.payload[:queue_duration_s]
- payload[:etag_route] = event.payload[:etag_route] if event.payload[:etag_route]
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = event.payload[Labkit::Correlation::CorrelationId::LOG_KEY] || Labkit::Correlation::CorrelationId.current_id
- CLOUDFLARE_CUSTOM_HEADERS.each do |_, value|
- payload[value] = event.payload[value] if event.payload[value]
- end
-
# https://github.com/roidrage/lograge#logging-errors--exceptions
exception = event.payload[:exception_object]
diff --git a/lib/gitlab/metrics/rails_slis.rb b/lib/gitlab/metrics/rails_slis.rb
index 69e0c1e9fde..a8bf216e452 100644
--- a/lib/gitlab/metrics/rails_slis.rb
+++ b/lib/gitlab/metrics/rails_slis.rb
@@ -30,10 +30,12 @@ module Gitlab
endpoint_id = API::Base.endpoint_id_for_route(route)
route_class = route.app.options[:for]
feature_category = route_class.feature_category_for_app(route.app)
+ request_urgency = route_class.urgency_for_app(route.app)
{
endpoint_id: endpoint_id,
- feature_category: feature_category
+ feature_category: feature_category,
+ request_urgency: request_urgency.name
}
end
end
@@ -42,7 +44,8 @@ module Gitlab
Gitlab::RequestEndpoints.all_controller_actions.map do |controller, action|
{
endpoint_id: controller.endpoint_id_for_action(action),
- feature_category: controller.feature_category_for_action(action)
+ feature_category: controller.feature_category_for_action(action),
+ request_urgency: controller.urgency_for_action(action).name
}
end
end
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index 3a0e34d5615..c976023c05a 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -116,9 +116,11 @@ module Gitlab
def record_apdex_if_needed(env, elapsed)
return unless Gitlab::Metrics::RailsSlis.request_apdex_counters_enabled?
+ urgency = urgency_for_env(env)
+
Gitlab::Metrics::RailsSlis.request_apdex.increment(
- labels: labels_from_context,
- success: satisfactory?(env, elapsed)
+ labels: labels_from_context.merge(request_urgency: urgency.name),
+ success: elapsed < urgency.duration
)
end
@@ -129,17 +131,15 @@ module Gitlab
}
end
- def satisfactory?(env, elapsed)
- target =
+ def urgency_for_env(env)
+ endpoint_urgency =
if env['api.endpoint'].present?
env['api.endpoint'].options[:for].try(:urgency_for_app, env['api.endpoint'])
elsif env['action_controller.instance'].present? && env['action_controller.instance'].respond_to?(:urgency)
env['action_controller.instance'].urgency
end
- target ||= Gitlab::EndpointAttributes::DEFAULT_URGENCY
-
- elapsed < target.duration
+ endpoint_urgency || Gitlab::EndpointAttributes::DEFAULT_URGENCY
end
end
end
diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb
index 560ca7da1a2..26a2a668cc1 100644
--- a/qa/qa/resource/base.rb
+++ b/qa/qa/resource/base.rb
@@ -81,7 +81,7 @@ module QA
result = yield.tap do
fabrication_time = Time.now - start
- Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time * 1000)
+ Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time)
Runtime::Logger.debug do
msg = ["==#{'=' * parents.size}>"]
msg << "Built a #{name}"
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 3f6a4eee5ac..26c800d470a 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -27,7 +27,9 @@ module QA
:import_error
attribute :group do
- Group.fabricate!
+ Group.fabricate! do |group|
+ group.api_client = api_client
+ end
end
attribute :path_with_namespace do
diff --git a/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb
index 3f72dd613e5..25c8683971b 100644
--- a/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/bulk_import_project_spec.rb
@@ -104,7 +104,10 @@ module QA
source_issue # fabricate source group, project, issue
end
- it 'successfully imports issue' do
+ it(
+ 'successfully imports issue',
+ testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/2325'
+ ) do
expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
aggregate_failures do
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index e9a49319f21..c4f93de5c23 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -501,11 +501,16 @@ RSpec.describe ApplicationController do
describe '#append_info_to_payload' do
controller(described_class) do
attr_reader :last_payload
+ urgency :high, [:foo]
def index
render html: 'authenticated'
end
+ def foo
+ render html: ''
+ end
+
def append_info_to_payload(payload)
super
@@ -513,6 +518,13 @@ RSpec.describe ApplicationController do
end
end
+ before do
+ routes.draw do
+ get 'index' => 'anonymous#index'
+ get 'foo' => 'anonymous#foo'
+ end
+ end
+
it 'does not log errors with a 200 response' do
get :index
@@ -534,6 +546,22 @@ RSpec.describe ApplicationController do
expect(controller.last_payload[:metadata]).to include('meta.user' => user.username)
end
+
+ context 'urgency information' do
+ it 'adds default urgency information to the payload' do
+ get :index
+
+ expect(controller.last_payload[:request_urgency]).to eq(:default)
+ expect(controller.last_payload[:target_duration_s]).to eq(1)
+ end
+
+ it 'adds customized urgency information to the payload' do
+ get :foo
+
+ expect(controller.last_payload[:request_urgency]).to eq(:high)
+ expect(controller.last_payload[:target_duration_s]).to eq(0.25)
+ end
+ end
end
describe '#access_denied' do
diff --git a/spec/frontend/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js
index 8878891701f..9f07eea433a 100644
--- a/spec/frontend/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/create_merge_request_dropdown_spec.js
@@ -46,7 +46,10 @@ describe('CreateMergeRequestDropdown', () => {
dropdown
.getRef('contains#hash')
.then(() => {
- expect(axios.get).toHaveBeenCalledWith(endpoint);
+ expect(axios.get).toHaveBeenCalledWith(
+ endpoint,
+ expect.objectContaining({ cancelToken: expect.anything() }),
+ );
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 0527c2153f4..9b63f84e617 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -702,23 +702,4 @@ describe('diffs/components/app', () => {
);
});
});
-
- describe('fluid layout', () => {
- beforeEach(() => {
- setFixtures(
- '<div><div class="merge-request-container limit-container-width container-limited"></div></div>',
- );
- });
-
- it('removes limited container classes when on diffs tab', () => {
- createComponent({ isFluidLayout: false, shouldShow: true }, () => {}, {
- glFeatures: { mrChangesFluidLayout: true },
- });
-
- const containerClassList = document.querySelector('.merge-request-container').classList;
-
- expect(containerClassList).not.toContain('container-limited');
- expect(containerClassList).not.toContain('limit-container-width');
- });
- });
});
diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js
index aa16b71172b..b3f177a1f12 100644
--- a/spec/frontend/projects/new/components/new_project_url_select_spec.js
+++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js
@@ -24,14 +24,23 @@ describe('NewProjectUrlSelect component', () => {
{
id: 'gid://gitlab/Group/26',
fullPath: 'flightjs',
+ name: 'Flight JS',
+ visibility: 'public',
+ webUrl: 'http://127.0.0.1:3000/flightjs',
},
{
id: 'gid://gitlab/Group/28',
fullPath: 'h5bp',
+ name: 'H5BP',
+ visibility: 'public',
+ webUrl: 'http://127.0.0.1:3000/h5bp',
},
{
id: 'gid://gitlab/Group/30',
fullPath: 'h5bp/subgroup',
+ name: 'H5BP Subgroup',
+ visibility: 'private',
+ webUrl: 'http://127.0.0.1:3000/h5bp/subgroup',
},
],
},
@@ -79,6 +88,10 @@ describe('NewProjectUrlSelect component', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const findHiddenInput = () => wrapper.find('input');
+ const clickDropdownItem = async () => {
+ wrapper.findComponent(GlDropdownItem).vm.$emit('click');
+ await wrapper.vm.$nextTick();
+ };
afterEach(() => {
wrapper.destroy();
@@ -127,7 +140,6 @@ describe('NewProjectUrlSelect component', () => {
it('focuses on the input when the dropdown is opened', async () => {
wrapper = mountComponent({ mountFn: mount });
-
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
@@ -140,7 +152,6 @@ describe('NewProjectUrlSelect component', () => {
it('renders expected dropdown items', async () => {
wrapper = mountComponent({ mountFn: mount });
-
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
@@ -160,7 +171,6 @@ describe('NewProjectUrlSelect component', () => {
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount });
-
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
@@ -195,23 +205,38 @@ describe('NewProjectUrlSelect component', () => {
};
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
-
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
expect(wrapper.find('li').text()).toBe('No matches found');
});
- it('updates hidden input with selected namespace', async () => {
+ it('emits `update-visibility` event to update the visibility radio options', async () => {
wrapper = mountComponent();
-
jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
- wrapper.findComponent(GlDropdownItem).vm.$emit('click');
+ const spy = jest.spyOn(eventHub, '$emit');
+ await clickDropdownItem();
+
+ const namespace = data.currentUser.groups.nodes[0];
+
+ expect(spy).toHaveBeenCalledWith('update-visibility', {
+ name: namespace.name,
+ visibility: namespace.visibility,
+ showPath: namespace.webUrl,
+ editPath: `${namespace.webUrl}/-/edit`,
+ });
+ });
+
+ it('updates hidden input with selected namespace', async () => {
+ wrapper = mountComponent();
+ jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
+ await clickDropdownItem();
+
expect(findHiddenInput().attributes()).toMatchObject({
name: 'project[namespace_id]',
value: getIdFromGraphQLId(data.currentUser.groups.nodes[0].id).toString(),
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
index 8243e2bd389..d9b7cd5afa2 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js
@@ -159,9 +159,8 @@ describe('LabelsSelect Mutations', () => {
labels = [
{ id: 1, title: 'scoped' },
{ id: 2, title: 'scoped::one', set: false },
- { id: 3, title: 'scoped::two', set: false },
- { id: 4, title: 'scoped::three', set: true },
- { id: 5, title: '' },
+ { id: 3, title: 'scoped::test', set: true },
+ { id: 4, title: '' },
];
});
@@ -192,9 +191,8 @@ describe('LabelsSelect Mutations', () => {
expect(state.labels).toEqual([
{ id: 1, title: 'scoped' },
{ id: 2, title: 'scoped::one', set: true, touched: true },
- { id: 3, title: 'scoped::two', set: false },
- { id: 4, title: 'scoped::three', set: false },
- { id: 5, title: '' },
+ { id: 3, title: 'scoped::test', set: false },
+ { id: 4, title: '' },
]);
});
});
diff --git a/spec/frontend/work_items/components/app_spec.js b/spec/frontend/work_items/components/app_spec.js
new file mode 100644
index 00000000000..95034085493
--- /dev/null
+++ b/spec/frontend/work_items/components/app_spec.js
@@ -0,0 +1,24 @@
+import { shallowMount } from '@vue/test-utils';
+import App from '~/work_items/components/app.vue';
+
+describe('Work Items Application', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(App, {
+ stubs: {
+ 'router-view': true,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders a component', () => {
+ createComponent();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
new file mode 100644
index 00000000000..efb4aa2feb2
--- /dev/null
+++ b/spec/frontend/work_items/mock_data.js
@@ -0,0 +1,17 @@
+export const workItemQueryResponse = {
+ workItem: {
+ __typename: 'WorkItem',
+ id: '1',
+ type: 'FEATURE',
+ widgets: {
+ __typename: 'WorkItemWidgetConnection',
+ nodes: [
+ {
+ __typename: 'TitleWidget',
+ type: 'TITLE',
+ contentText: 'Test',
+ },
+ ],
+ },
+ },
+};
diff --git a/spec/frontend/work_items/pages/work_item_root_spec.js b/spec/frontend/work_items/pages/work_item_root_spec.js
new file mode 100644
index 00000000000..64d02baed36
--- /dev/null
+++ b/spec/frontend/work_items/pages/work_item_root_spec.js
@@ -0,0 +1,70 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
+import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
+import { workItemQueryResponse } from '../mock_data';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+const WORK_ITEM_ID = '1';
+
+describe('Work items root component', () => {
+ let wrapper;
+ let fakeApollo;
+
+ const findTitle = () => wrapper.find('[data-testid="title"]');
+
+ const createComponent = ({ queryResponse = workItemQueryResponse } = {}) => {
+ fakeApollo = createMockApollo();
+ fakeApollo.clients.defaultClient.cache.writeQuery({
+ query: workItemQuery,
+ variables: {
+ id: WORK_ITEM_ID,
+ },
+ data: queryResponse,
+ });
+
+ wrapper = shallowMount(WorkItemsRoot, {
+ propsData: {
+ id: WORK_ITEM_ID,
+ },
+ localVue,
+ apolloProvider: fakeApollo,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ fakeApollo = null;
+ });
+
+ it('renders the title if title is in the widgets list', () => {
+ createComponent();
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().text()).toBe('Test');
+ });
+
+ it('does not render the title if title is not in the widgets list', () => {
+ const queryResponse = {
+ workItem: {
+ ...workItemQueryResponse.workItem,
+ widgets: {
+ __typename: 'WorkItemWidgetConnection',
+ nodes: [
+ {
+ __typename: 'SomeOtherWidget',
+ type: 'OTHER',
+ contentText: 'Test',
+ },
+ ],
+ },
+ },
+ };
+ createComponent({ queryResponse });
+
+ expect(findTitle().exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
new file mode 100644
index 00000000000..0a57eab753f
--- /dev/null
+++ b/spec/frontend/work_items/router_spec.js
@@ -0,0 +1,30 @@
+import { mount } from '@vue/test-utils';
+import App from '~/work_items/components/app.vue';
+import WorkItemsRoot from '~/work_items/pages/work_item_root.vue';
+import { createRouter } from '~/work_items/router';
+
+describe('Work items router', () => {
+ let wrapper;
+
+ const createComponent = async (routeArg) => {
+ const router = createRouter('/work_item');
+ if (routeArg !== undefined) {
+ await router.push(routeArg);
+ }
+
+ wrapper = mount(App, {
+ router,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ window.location.hash = '';
+ });
+
+ it('renders work item on `/1` route', async () => {
+ await createComponent('/1');
+
+ expect(wrapper.find(WorkItemsRoot).exists()).toBe(true);
+ });
+});
diff --git a/spec/lib/gitlab/ci/variables/builder_spec.rb b/spec/lib/gitlab/ci/variables/builder_spec.rb
new file mode 100644
index 00000000000..10275f33484
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/builder_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Variables::Builder do
+ let(:builder) { described_class.new(pipeline) }
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ describe '#scoped_variables' do
+ let(:environment) { job.expanded_environment_name }
+ let(:dependencies) { true }
+
+ subject { builder.scoped_variables(job, environment: environment, dependencies: dependencies) }
+
+ it 'returns the expected variables' do
+ keys = %w[CI_JOB_NAME
+ CI_JOB_STAGE
+ CI_NODE_TOTAL
+ CI_BUILD_NAME
+ CI_BUILD_STAGE]
+
+ subject.map { |env| env[:key] }.tap do |names|
+ expect(names).to include(*keys)
+ end
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(ci_predefined_vars_in_builder: false)
+ end
+
+ it 'returns no variables' do
+ expect(subject.map { |env| env[:key] }).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/grape_logging/loggers/urgency_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/urgency_logger_spec.rb
new file mode 100644
index 00000000000..464534f0271
--- /dev/null
+++ b/spec/lib/gitlab/grape_logging/loggers/urgency_logger_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GrapeLogging::Loggers::UrgencyLogger do
+ def endpoint(options, namespace: '')
+ Struct.new(:options, :namespace).new(options, namespace)
+ end
+
+ let(:api_class) do
+ Class.new(API::Base) do
+ namespace 'testing' do
+ # rubocop:disable Rails/HttpPositionalArguments
+ # This is not the get that performs a request, but the one from Grape
+ get 'test', urgency: :high do
+ {}
+ end
+ # rubocop:enable Rails/HttpPositionalArguments
+ end
+ end
+ end
+
+ describe ".parameters" do
+ where(:request_env, :expected_parameters) do
+ [
+ [{}, {}],
+ [{ 'api.endpoint' => endpoint({}) }, {}],
+ [{ 'api.endpoint' => endpoint({ for: 'something weird' }) }, {}],
+ [
+ { 'api.endpoint' => endpoint({ for: api_class, path: [] }) },
+ { request_urgency: :default, target_duration_s: 1 }
+ ],
+ [
+ { 'api.endpoint' => endpoint({ for: api_class, path: ['test'] }, namespace: '/testing') },
+ { request_urgency: :high, target_duration_s: 0.25 }
+ ]
+ ]
+ end
+
+ with_them do
+ let(:request) { double('request', env: request_env) }
+
+ subject { described_class.new.parameters(request, nil) }
+
+ it { is_expected.to eq(expected_parameters) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
index 43e890a6c4f..145d573b6de 100644
--- a/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/redis/redis_check_spec.rb
@@ -4,5 +4,5 @@ require 'spec_helper'
require_relative '../simple_check_shared'
RSpec.describe Gitlab::HealthChecks::Redis::RedisCheck do
- include_examples 'simple_check', 'redis_ping', 'Redis', 'PONG'
+ include_examples 'simple_check', 'redis_ping', 'Redis', true
end
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
index 59c4e1083ae..0d4c0545ae2 100644
--- a/spec/lib/gitlab/import_export/command_line_util_spec.rb
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -17,6 +17,10 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
def initialize
@shared = Gitlab::ImportExport::Shared.new(nil)
end
+
+ def download(url, upload_path)
+ super(url, upload_path)
+ end
end.new
end
@@ -101,4 +105,44 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
end
end
end
+
+ describe '#download' do
+ before do
+ stub_request(:get, loc)
+ .to_return(
+ status: 200,
+ body: content
+ )
+ end
+
+ context 'a non-localhost uri' do
+ let(:loc) { 'https://gitlab.com' }
+ let(:content) { File.open('spec/fixtures/rails_sample.tif') }
+
+ it 'gets the contents' do
+ Tempfile.create("foo") do |f|
+ subject.download(loc, f.path)
+ expect(f.read).to eq(File.open('spec/fixtures/rails_sample.tif').read)
+ end
+ end
+
+ it 'streams the contents' do
+ expect(Gitlab::HTTP).to receive(:get).with(loc, hash_including(stream_body: true))
+ Tempfile.create("foo") do |f|
+ subject.download(loc, f.path)
+ end
+ end
+ end
+
+ context 'a localhost uri' do
+ let(:loc) { 'https://localhost:8081/foo/bar' }
+ let(:content) { 'foo' }
+
+ it 'throws a blocked url error' do
+ Tempfile.create("foo") do |f|
+ expect { subject.download(loc, f.path) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index 9daedfc37e4..a4ae39a835a 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -19,7 +19,13 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
user_id: 'test',
cf_ray: SecureRandom.hex,
cf_request_id: SecureRandom.hex,
- metadata: { 'meta.user' => 'jane.doe' }
+ metadata: { 'meta.user' => 'jane.doe' },
+ request_urgency: :default,
+ target_duration_s: 1,
+ remote_ip: '192.168.1.2',
+ ua: 'Nyxt',
+ queue_duration_s: 0.2,
+ etag_route: '/etag'
}
end
@@ -66,6 +72,18 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
end
end
+ context 'trusted payload' do
+ it { is_expected.to include(event_payload.slice(*described_class::KNOWN_PAYLOAD_PARAMS)) }
+
+ context 'payload with rejected fields' do
+ let(:event_payload) { { params: {}, request_urgency: :high, something: 'random', username: nil } }
+
+ it { is_expected.to include({ request_urgency: :high }) }
+ it { is_expected.not_to include({ something: 'random' }) }
+ it { is_expected.not_to include({ username: nil }) }
+ end
+ end
+
context 'when correlation_id is overridden' do
let(:correlation_id_key) { Labkit::Correlation::CorrelationId::LOG_KEY }
diff --git a/spec/lib/gitlab/metrics/rails_slis_spec.rb b/spec/lib/gitlab/metrics/rails_slis_spec.rb
index 16fcb9d46a2..4409bc13afe 100644
--- a/spec/lib/gitlab/metrics/rails_slis_spec.rb
+++ b/spec/lib/gitlab/metrics/rails_slis_spec.rb
@@ -17,11 +17,13 @@ RSpec.describe Gitlab::Metrics::RailsSlis do
possible_labels = [
{
endpoint_id: "GET /api/:version/version",
- feature_category: :not_owned
+ feature_category: :not_owned,
+ request_urgency: :default
},
{
endpoint_id: "ProjectsController#show",
- feature_category: :projects
+ feature_category: :projects,
+ request_urgency: :default
}
]
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index 5870f9a8f68..1145bda3570 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -36,7 +36,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it 'tracks request count and duration' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time)
- expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment)
+ .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true)
subject.call(env)
end
@@ -122,7 +123,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'issue_tracking')
expect(described_class).not_to receive(:http_health_requests_total)
expect(Gitlab::Metrics::RailsSlis.request_apdex)
- .to receive(:increment).with(labels: { feature_category: 'issue_tracking', endpoint_id: 'IssuesController#show' }, success: true)
+ .to receive(:increment).with(labels: { feature_category: 'issue_tracking', endpoint_id: 'IssuesController#show', request_urgency: :default }, success: true)
subject.call(env)
end
@@ -156,7 +157,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it 'sets the required labels to unknown' do
expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown')
expect(described_class).not_to receive(:http_health_requests_total)
- expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, success: true)
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment)
+ .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true)
subject.call(env)
end
@@ -206,7 +208,11 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it "captures SLI metrics" do
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'hello_world', endpoint_id: 'GET /projects/:id/archive' },
+ labels: {
+ feature_category: 'hello_world',
+ endpoint_id: 'GET /projects/:id/archive',
+ request_urgency: request_urgency_name
+ },
success: success
)
subject.call(env)
@@ -235,7 +241,11 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
it "captures SLI metrics" do
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'hello_world', endpoint_id: 'AnonymousController#index' },
+ labels: {
+ feature_category: 'hello_world',
+ endpoint_id: 'AnonymousController#index',
+ request_urgency: request_urgency_name
+ },
success: success
)
subject.call(env)
@@ -255,17 +265,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
let(:api_handler) { Class.new(::API::Base) }
- it "falls back request's expectation to medium (1 second)" do
+ it "falls back request's expectation to default (1 second)" do
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: true
)
subject.call(env)
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: false
)
subject.call(env)
@@ -281,17 +299,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
{ 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
end
- it "falls back request's expectation to medium (1 second)" do
+ it "falls back request's expectation to default (1 second)" do
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: true
)
subject.call(env)
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: false
)
subject.call(env)
@@ -303,17 +329,25 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
{ 'REQUEST_METHOD' => 'GET' }
end
- it "falls back request's expectation to medium (1 second)" do
+ it "falls back request's expectation to default (1 second)" do
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100.9)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: true
)
subject.call(env)
allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101)
expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
- labels: { feature_category: 'unknown', endpoint_id: 'unknown' },
+ labels: {
+ feature_category: 'unknown',
+ endpoint_id: 'unknown',
+ request_urgency: :default
+ },
success: false
)
subject.call(env)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 2ebf75a1d8a..cbf73cdb8b2 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -2759,7 +2759,10 @@ RSpec.describe Ci::Build do
let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } }
before do
- allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
+ allow(builder).to receive(:predefined_variables) { [build_pre_var] }
+ end
+
allow(build).to receive(:yaml_variables) { [build_yaml_var] }
allow(build).to receive(:persisted_variables) { [] }
allow(build).to receive(:job_jwt_variables) { [job_jwt_var] }
@@ -3411,75 +3414,122 @@ RSpec.describe Ci::Build do
end
describe '#scoped_variables' do
- context 'when build has not been persisted yet' do
- let(:build) do
- described_class.new(
- name: 'rspec',
- stage: 'test',
- ref: 'feature',
- project: project,
- pipeline: pipeline,
- scheduling_type: :stage
- )
- end
+ before do
+ pipeline.clear_memoization(:predefined_vars_in_builder_enabled)
+ end
- let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
+ it 'records a prometheus metric' do
+ histogram = double(:histogram)
+ expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_builder_scoped_variables_histogram)
+ .and_return(histogram)
- it 'does not persist the build' do
- expect(build).to be_valid
- expect(build).not_to be_persisted
+ expect(histogram).to receive(:observe)
+ .with({}, a_kind_of(ActiveSupport::Duration))
- build.scoped_variables
+ build.scoped_variables
+ end
- expect(build).not_to be_persisted
- end
+ shared_examples 'calculates scoped_variables' do
+ context 'when build has not been persisted yet' do
+ let(:build) do
+ described_class.new(
+ name: 'rspec',
+ stage: 'test',
+ ref: 'feature',
+ project: project,
+ pipeline: pipeline,
+ scheduling_type: :stage
+ )
+ end
- it 'returns static predefined variables' do
- keys = %w[CI_JOB_NAME
- CI_COMMIT_SHA
- CI_COMMIT_SHORT_SHA
- CI_COMMIT_REF_NAME
- CI_COMMIT_REF_SLUG
- CI_JOB_STAGE]
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') }
- variables = build.scoped_variables
+ it 'does not persist the build' do
+ expect(build).to be_valid
+ expect(build).not_to be_persisted
- variables.map { |env| env[:key] }.tap do |names|
- expect(names).to include(*keys)
+ build.scoped_variables
+
+ expect(build).not_to be_persisted
end
- expect(variables)
- .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
+ it 'returns static predefined variables' do
+ keys = %w[CI_JOB_NAME
+ CI_COMMIT_SHA
+ CI_COMMIT_SHORT_SHA
+ CI_COMMIT_REF_NAME
+ CI_COMMIT_REF_SLUG
+ CI_JOB_STAGE]
+
+ variables = build.scoped_variables
+
+ variables.map { |env| env[:key] }.tap do |names|
+ expect(names).to include(*keys)
+ end
+
+ expect(variables)
+ .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false)
+ end
+
+ it 'does not return prohibited variables' do
+ keys = %w[CI_JOB_ID
+ CI_JOB_URL
+ CI_JOB_TOKEN
+ CI_BUILD_ID
+ CI_BUILD_TOKEN
+ CI_REGISTRY_USER
+ CI_REGISTRY_PASSWORD
+ CI_REPOSITORY_URL
+ CI_ENVIRONMENT_URL
+ CI_DEPLOY_USER
+ CI_DEPLOY_PASSWORD]
+
+ build.scoped_variables.map { |env| env[:key] }.tap do |names|
+ expect(names).not_to include(*keys)
+ end
+ end
end
- it 'does not return prohibited variables' do
- keys = %w[CI_JOB_ID
- CI_JOB_URL
- CI_JOB_TOKEN
- CI_BUILD_ID
- CI_BUILD_TOKEN
- CI_REGISTRY_USER
- CI_REGISTRY_PASSWORD
- CI_REPOSITORY_URL
- CI_ENVIRONMENT_URL
- CI_DEPLOY_USER
- CI_DEPLOY_PASSWORD]
+ context 'with dependency variables' do
+ let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) }
+ let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) }
- build.scoped_variables.map { |env| env[:key] }.tap do |names|
- expect(names).not_to include(*keys)
+ let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
+
+ it 'inherits dependent variables' do
+ expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
end
end
end
- context 'with dependency variables' do
- let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) }
- let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) }
+ it_behaves_like 'calculates scoped_variables'
- let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) }
+ it 'delegates to the variable builders' do
+ expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
+ expect(builder)
+ .to receive(:scoped_variables).with(build, hash_including(:environment, :dependencies))
+ .and_call_original
- it 'inherits dependent variables' do
- expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value)
+ expect(builder).to receive(:predefined_variables).and_call_original
end
+
+ build.scoped_variables
+ end
+
+ context 'when ci builder feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_predefined_vars_in_builder: false)
+ end
+
+ it 'does not delegate to the variable builders' do
+ expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder|
+ expect(builder).not_to receive(:predefined_variables)
+ end
+
+ build.scoped_variables
+ end
+
+ it_behaves_like 'calculates scoped_variables'
end
end