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-09-28 15:11:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-28 15:11:10 +0300
commit24e5ef9b1a56019d7ea6deb41e8c8cbe5f0a59a3 (patch)
tree9590894b19bd66573068c5dce417939f14aa7590
parentf4963c8c9bb2b6c38e9bd3016494a27c6c91e7e6 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/images/logos/zentao.svg14
-rw-r--r--app/assets/javascripts/api.js4
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue13
-rw-r--r--app/assets/javascripts/notes/stores/actions.js4
-rw-r--r--app/assets/javascripts/notes/stores/getters.js11
-rw-r--r--app/assets/javascripts/repository/components/upload_blob_modal.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue88
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue2
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss4
-rw-r--r--app/controllers/application_controller.rb14
-rw-r--r--app/controllers/projects/security/configuration_controller.rb2
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/helpers/application_settings_helper.rb6
-rw-r--r--app/helpers/tab_helper.rb69
-rw-r--r--app/models/application_setting.rb4
-rw-r--r--app/models/application_setting_implementation.rb6
-rw-r--r--app/views/admin/application_settings/network.html.haml12
-rw-r--r--app/views/admin/projects/index.html.haml15
-rw-r--r--app/views/admin/users/_head.html.haml17
-rw-r--r--db/migrate/20210914145810_add_throttle_deprecated_api_columns.rb13
-rw-r--r--db/schema_migrations/202109141458101
-rw-r--r--db/structure.sql6
-rw-r--r--doc/administration/instance_limits.md9
-rw-r--r--doc/development/fe_guide/storybook.md2
-rw-r--r--doc/development/graphql_guide/pagination.md6
-rw-r--r--doc/development/snowplow/index.md4
-rw-r--r--doc/development/stage_group_dashboards.md2
-rw-r--r--doc/development/testing_guide/end_to_end/best_practices.md6
-rw-r--r--doc/security/rate_limits.md1
-rw-r--r--doc/user/admin_area/settings/deprecated_api_rate_limits.md53
-rw-r--r--doc/user/admin_area/settings/index.md1
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md2
-rw-r--r--doc/user/clusters/agent/repository.md4
-rw-r--r--lib/api/base.rb13
-rw-r--r--lib/api/internal/base.rb2
-rw-r--r--lib/api/unleash.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/endpoint_attributes.rb48
-rw-r--r--lib/gitlab/endpoint_attributes/config.rb81
-rw-r--r--lib/gitlab/metrics/requests_rack_middleware.rb21
-rw-r--r--lib/gitlab/metrics/subscribers/rack_attack.rb3
-rw-r--r--lib/gitlab/quick_actions/relate_actions.rb14
-rw-r--r--lib/gitlab/rack_attack/request.rb23
-rw-r--r--lib/gitlab/request_endpoints.rb2
-rw-r--r--lib/gitlab/throttle.rb2
-rw-r--r--lib/gitlab/with_feature_category.rb50
-rw-r--r--locale/gitlab.pot26
-rw-r--r--qa/qa/page/merge_request/show.rb5
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb2
-rw-r--r--spec/controllers/every_controller_spec.rb6
-rw-r--r--spec/features/admin/admin_settings_spec.rb7
-rw-r--r--spec/features/merge_request/user_suggests_changes_on_diff_spec.rb10
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb14
-rw-r--r--spec/features/projects/show/user_uploads_files_spec.rb22
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js34
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap11
-rw-r--r--spec/helpers/tab_helper_spec.rb54
-rw-r--r--spec/lib/api/base_spec.rb92
-rw-r--r--spec/lib/gitlab/endpoint_attributes_spec.rb132
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb161
-rw-r--r--spec/lib/gitlab/rack_attack/request_spec.rb31
-rw-r--r--spec/lib/gitlab/request_endpoints_spec.rb4
-rw-r--r--spec/lib/gitlab/with_feature_category_spec.rb69
-rw-r--r--spec/models/application_setting_spec.rb4
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb12
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb34
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb8
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb24
-rw-r--r--spec/requests/api/graphql/users_spec.rb12
-rw-r--r--spec/requests/rack_attack_global_spec.rb213
-rw-r--r--spec/services/application_settings/update_service_spec.rb26
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb15
-rw-r--r--spec/support/database/cross-join-allowlist.yml1
-rw-r--r--spec/support/matchers/be_a_target_duration.rb7
-rw-r--r--spec/support/shared_examples/features/project_upload_files_shared_examples.rb50
-rw-r--r--spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb18
-rw-r--r--spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb7
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb5
87 files changed, 1463 insertions, 375 deletions
diff --git a/app/assets/images/logos/zentao.svg b/app/assets/images/logos/zentao.svg
new file mode 100644
index 00000000000..d2115b72aee
--- /dev/null
+++ b/app/assets/images/logos/zentao.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" fill="none">
+ <path fill="url(#SVGID_1_)" d="M8,1C4.1,1,1,4.1,1,8s3.1,7,7,7s7-3.1,7-7S11.9,1,8,1L8,1z M11.3,8.2C9.8,7.7,7.9,6.1,5.8,7.6
+ C4,8.9,4.8,11.1,6,11.8c0.9,0.6,2.3,0.7,3,0.1C9.7,11.4,10,10,9,9.5C8.6,9.4,7.9,9.3,7.5,9.8c-0.5,0.6-0.3,1.4,0.4,1.7
+ c0,0-1.2-0.1-1.4-1.3C6.2,7.9,9,7.6,10.3,8.4c2.4,1.5,1.5,4.8-2,5.4c-1.8,0.3-4.8-0.3-5.9-2.7c-0.4-0.9-0.3-0.7-0.3-0.7
+ c0.1,0.1,0.3,0.3,0.4,0.4c0.8,0.6,1.6,0.1,1.4-0.8C3.3,7.2,4.4,6.7,5.1,6.2s0.4-1.5-0.9-1.3c-1.9,0.3-2.4,3-2.4,3s-0.3-4.6,3.7-5
+ c4.1-0.4,4.7,3.2,6.5,3.7c2.5,0.8,1.3-2.6,1.3-2.6s1.1,1.7,0.6,3.2C13.5,8.2,12.3,8.5,11.3,8.2z" />
+ <defs>
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="8" y1="-271.1102" x2="8" y2="-257.1102"
+ gradientTransform="matrix(1 0 0 -1 0 -256.1102)">
+ <stop offset="0" style="stop-color:#445470" />
+ <stop offset="1" style="stop-color:#7A869A" />
+ </linearGradient>
+ </defs>
+</svg>
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 01e463c1965..adf3e122a64 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -499,10 +499,10 @@ const Api = {
return axios.put(url, params);
},
- applySuggestionBatch(ids) {
+ applySuggestionBatch(ids, message) {
const url = Api.buildUrl(Api.applySuggestionBatchPath);
- return axios.put(url, { ids });
+ return axios.put(url, { ids, commit_message: message });
},
commitPipelines(projectId, sha) {
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 93f71276120..1ce1696e332 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -51,7 +51,7 @@ export default {
},
},
computed: {
- ...mapGetters(['getDiscussion', 'suggestionsCount']),
+ ...mapGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']),
...mapGetters('diffs', ['suggestionCommitMessage']),
discussion() {
if (!this.note.isDraft) return {};
@@ -74,9 +74,10 @@ export default {
// Please see this issue comment for why these
// are hard-coded to 1:
// https://gitlab.com/gitlab-org/gitlab/-/issues/291027#note_468308022
- const suggestionsCount = 1;
- const filesCount = 1;
- const filePaths = this.file ? [this.file.file_path] : [];
+ const suggestionsCount = this.batchSuggestionsInfo.length || 1;
+ const batchFilePaths = this.getSuggestionsFilePaths();
+ const filePaths = batchFilePaths.length ? batchFilePaths : [this.file.file_path];
+ const filesCount = filePaths.length;
const suggestion = this.suggestionCommitMessage({
file_paths: filePaths.join(', '),
suggestions_count: suggestionsCount,
@@ -131,8 +132,8 @@ export default {
message,
}).then(callback);
},
- applySuggestionBatch({ flashContainer }) {
- return this.submitSuggestionBatch({ flashContainer });
+ applySuggestionBatch({ message, flashContainer }) {
+ return this.submitSuggestionBatch({ message, flashContainer });
},
addSuggestionToBatch(suggestionId) {
const { discussion_id: discussionId, id: noteId } = this.note;
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 656591e0c32..7eb10f647a0 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -631,7 +631,7 @@ export const submitSuggestion = (
});
};
-export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContainer }) => {
+export const submitSuggestionBatch = ({ commit, dispatch, state }, { message, flashContainer }) => {
const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId);
const resolveAllDiscussions = () =>
@@ -644,7 +644,7 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContai
commit(types.SET_RESOLVING_DISCUSSION, true);
dispatch('stopPolling');
- return Api.applySuggestionBatch(suggestionIds)
+ return Api.applySuggestionBatch(suggestionIds, message)
.then(() => Promise.all(resolveAllDiscussions()))
.then(() => commit(types.CLEAR_SUGGESTION_BATCH))
.catch((err) => {
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 956221d69ae..a710ac0ccf5 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -283,3 +283,14 @@ export const suggestionsCount = (state, getters) =>
export const hasDrafts = (state, getters, rootState, rootGetters) =>
Boolean(rootGetters['batchComments/hasDrafts']);
+
+export const getSuggestionsFilePaths = (state) => () =>
+ state.batchSuggestionsInfo.reduce((acc, suggestion) => {
+ const discussion = state.discussions.find((d) => d.id === suggestion.discussionId);
+
+ if (acc.indexOf(discussion?.diff_file?.file_path) === -1) {
+ acc.push(discussion.diff_file.file_path);
+ }
+
+ return acc;
+ }, []);
diff --git a/app/assets/javascripts/repository/components/upload_blob_modal.vue b/app/assets/javascripts/repository/components/upload_blob_modal.vue
index df5a5ea6163..0199b893453 100644
--- a/app/assets/javascripts/repository/components/upload_blob_modal.vue
+++ b/app/assets/javascripts/repository/components/upload_blob_modal.vue
@@ -220,6 +220,7 @@ export default {
class="gl-h-200! gl-mb-4"
single-file-selection
:valid-file-mimetypes="$options.validFileMimetypes"
+ :is-file-valid="() => true"
@change="setFile"
>
<div
diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
index d6a20984ad1..ce7cbafb97d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
@@ -1,5 +1,6 @@
<script>
import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui';
+import { __, n__ } from '~/locale';
export default {
components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton },
@@ -13,12 +14,26 @@ export default {
type: String,
required: true,
},
+ batchSuggestionsCount: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
},
data() {
return {
message: null,
};
},
+ computed: {
+ dropdownText() {
+ if (this.batchSuggestionsCount <= 1) {
+ return __('Apply suggestion');
+ }
+
+ return n__('Apply %d suggestion', 'Apply %d suggestions', this.batchSuggestionsCount);
+ },
+ },
methods: {
onApply() {
this.$emit('apply', this.message);
@@ -29,10 +44,11 @@ export default {
<template>
<gl-dropdown
- :text="__('Apply suggestion')"
+ :text="dropdownText"
:disabled="disabled"
boundary="window"
right
+ lazy
menu-class="gl-w-full!"
data-qa-selector="apply_suggestion_dropdown"
@shown="$refs.commitMessage.$el.focus()"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index 9c954fce322..7d8d8c0b90e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -54,8 +54,8 @@ export default {
applySuggestion(callback, message) {
this.$emit('apply', { suggestionId: this.suggestion.id, callback, message });
},
- applySuggestionBatch() {
- this.$emit('applyBatch');
+ applySuggestionBatch(message) {
+ this.$emit('applyBatch', message);
},
addSuggestionToBatch() {
this.$emit('addToBatch', this.suggestion.id);
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 5fdef0b1a23..c236b7ad6b1 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -58,12 +58,19 @@ export default {
isApplyingSingle: false,
};
},
+
computed: {
isApplying() {
return this.isApplyingSingle || this.isApplyingBatch;
},
tooltipMessage() {
- return this.canApply ? __('This also resolves this thread') : this.inapplicableReason;
+ if (!this.canApply) {
+ return this.inapplicableReason;
+ }
+
+ return this.batchSuggestionsCount > 1
+ ? __('This also resolves all related threads')
+ : __('This also resolves this thread');
},
isDisableButton() {
return this.isApplying || !this.canApply;
@@ -72,13 +79,30 @@ export default {
if (this.isApplyingSingle || this.batchSuggestionsCount < 2) {
return __('Applying suggestion...');
}
+
return __('Applying suggestions...');
},
isLoggedIn() {
return isLoggedIn();
},
+ showApplySuggestion() {
+ if (!this.isLoggedIn) return false;
+
+ if (this.batchSuggestionsCount >= 1 && !this.isBatched) {
+ return false;
+ }
+
+ return true;
+ },
},
methods: {
+ apply(message) {
+ if (this.batchSuggestionsCount > 1) {
+ this.applySuggestionBatch(message);
+ } else {
+ this.applySuggestion(message);
+ }
+ },
applySuggestion(message) {
if (!this.canApply) return;
this.isApplyingSingle = true;
@@ -88,9 +112,9 @@ export default {
applySuggestionCallback() {
this.isApplyingSingle = false;
},
- applySuggestionBatch() {
+ applySuggestionBatch(message) {
if (!this.canApply) return;
- this.$emit('applyBatch');
+ this.$emit('applyBatch', message);
},
addSuggestionToBatch() {
this.$emit('addToBatch');
@@ -115,45 +139,35 @@ export default {
<gl-loading-icon size="sm" class="d-flex-center mr-2" />
<span>{{ applyingSuggestionsMessage }}</span>
</div>
- <div v-else-if="canApply && isBatched" class="d-flex align-items-center">
- <gl-button
- class="btn-inverted js-remove-from-batch-btn btn-grouped"
- :disabled="isApplying"
- @click="removeSuggestionFromBatch"
- >
- {{ __('Remove from batch') }}
- </gl-button>
- <gl-button
- v-gl-tooltip.viewport="__('This also resolves all related threads')"
- class="btn-inverted js-apply-batch-btn btn-grouped"
- data-qa-selector="apply_suggestions_batch_button"
- :disabled="isApplying"
- variant="success"
- @click="applySuggestionBatch"
- >
- {{ __('Apply suggestions') }}
- <span class="badge badge-pill badge-pill-success">
- {{ batchSuggestionsCount }}
- </span>
- </gl-button>
- </div>
- <div v-else class="d-flex align-items-center">
- <gl-button
- v-if="suggestionsCount > 1 && !isDisableButton"
- class="btn-inverted js-add-to-batch-btn btn-grouped"
- data-qa-selector="add_suggestion_batch_button"
- :disabled="isDisableButton"
- @click="addSuggestionToBatch"
- >
- {{ __('Add suggestion to batch') }}
- </gl-button>
+ <div v-else-if="canApply" class="d-flex align-items-center">
+ <div v-if="isBatched">
+ <gl-button
+ class="btn-inverted js-remove-from-batch-btn btn-grouped"
+ :disabled="isApplying"
+ @click="removeSuggestionFromBatch"
+ >
+ {{ __('Remove from batch') }}
+ </gl-button>
+ </div>
+ <div v-else>
+ <gl-button
+ v-if="!isDisableButton && suggestionsCount > 1"
+ class="btn-inverted js-add-to-batch-btn btn-grouped"
+ data-qa-selector="add_suggestion_batch_button"
+ :disabled="isDisableButton"
+ @click="addSuggestionToBatch"
+ >
+ {{ __('Add suggestion to batch') }}
+ </gl-button>
+ </div>
<apply-suggestion
- v-if="isLoggedIn"
+ v-if="showApplySuggestion"
v-gl-tooltip.viewport="tooltipMessage"
:disabled="isDisableButton"
:default-commit-message="defaultCommitMessage"
+ :batch-suggestions-count="batchSuggestionsCount"
class="gl-ml-3"
- @apply="applySuggestion"
+ @apply="apply"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 63774c6c498..e36cfb3b275 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -68,6 +68,10 @@ export default {
if (this.suggestionsWatch) {
this.suggestionsWatch();
}
+
+ if (this.defaultCommitMessageWatch) {
+ this.defaultCommitMessageWatch();
+ }
},
methods: {
renderSuggestions() {
@@ -123,12 +127,16 @@ export default {
suggestionDiff.suggestionsCount = this.suggestionsCount;
});
+ this.defaultCommitMessageWatch = this.$watch('defaultCommitMessage', () => {
+ suggestionDiff.defaultCommitMessage = this.defaultCommitMessage;
+ });
+
suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => {
this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message });
});
- suggestionDiff.$on('applyBatch', () => {
- this.$emit('applyBatch', { flashContainer: this.$el });
+ suggestionDiff.$on('applyBatch', (message) => {
+ this.$emit('applyBatch', { message, flashContainer: this.$el });
});
suggestionDiff.$on('addToBatch', (suggestionId) => {
diff --git a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
index afb1ea702fa..0a7a22ed3a8 100644
--- a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
+++ b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue
@@ -45,7 +45,7 @@ export default {
data() {
return {
dragCounter: 0,
- isDragDataValid: false,
+ isDragDataValid: true,
};
},
computed: {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 7315bce1ed9..ef294635641 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -153,6 +153,10 @@
vertical-align: middle;
margin-bottom: 3px;
}
+
+ .dropdown-chevron {
+ margin-bottom: 0;
+ }
}
@include media-breakpoint-down(xs) {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 4742760ea10..87a1db0dc52 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -21,7 +21,7 @@ class ApplicationController < ActionController::Base
include Impersonation
include Gitlab::Logging::CloudflareHelper
include Gitlab::Utils::StrongMemoize
- include ::Gitlab::WithFeatureCategory
+ include ::Gitlab::EndpointAttributes
include FlocOptOut
before_action :authenticate_user!, except: [:route_not_found]
@@ -135,6 +135,14 @@ class ApplicationController < ActionController::Base
end
end
+ def feature_category
+ self.class.feature_category_for_action(action_name).to_s
+ end
+
+ def target_duration
+ self.class.target_duration_for_action(action_name)
+ end
+
protected
def workhorse_excluded_content_types
@@ -547,10 +555,6 @@ class ApplicationController < ActionController::Base
auth_user if strong_memoized?(:auth_user)
end
- def feature_category
- self.class.feature_category_for_action(action_name).to_s
- end
-
def required_signup_info
return unless current_user
return unless current_user.role_required?
diff --git a/app/controllers/projects/security/configuration_controller.rb b/app/controllers/projects/security/configuration_controller.rb
index 19de157357a..444f4783a19 100644
--- a/app/controllers/projects/security/configuration_controller.rb
+++ b/app/controllers/projects/security/configuration_controller.rb
@@ -5,7 +5,7 @@ module Projects
class ConfigurationController < Projects::ApplicationController
include SecurityAndCompliancePermissions
- feature_category :static_application_security_testing
+ feature_category :static_application_security_testing, [:show]
def show
render_403 unless can?(current_user, :read_security_configuration, project)
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 5f1b3750e41..63ddebbdae9 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -23,6 +23,7 @@ class SearchController < ApplicationController
layout 'search'
feature_category :global_search
+ target_duration :very_fast, [:opensearch]
def show
@project = search_service.project
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 6f3114e1b83..b2fd76ff0c2 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -333,6 +333,9 @@ module ApplicationSettingsHelper
:throttle_authenticated_files_api_enabled,
:throttle_authenticated_files_api_period_in_seconds,
:throttle_authenticated_files_api_requests_per_period,
+ :throttle_authenticated_deprecated_api_enabled,
+ :throttle_authenticated_deprecated_api_period_in_seconds,
+ :throttle_authenticated_deprecated_api_requests_per_period,
:throttle_unauthenticated_api_enabled,
:throttle_unauthenticated_api_period_in_seconds,
:throttle_unauthenticated_api_requests_per_period,
@@ -345,6 +348,9 @@ module ApplicationSettingsHelper
:throttle_unauthenticated_files_api_enabled,
:throttle_unauthenticated_files_api_period_in_seconds,
:throttle_unauthenticated_files_api_requests_per_period,
+ :throttle_unauthenticated_deprecated_api_enabled,
+ :throttle_unauthenticated_deprecated_api_period_in_seconds,
+ :throttle_unauthenticated_deprecated_api_requests_per_period,
:throttle_protected_paths_enabled,
:throttle_protected_paths_period_in_seconds,
:throttle_protected_paths_requests_per_period,
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index e64e1c935dd..05b7a53a38d 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -1,6 +1,67 @@
# frozen_string_literal: true
module TabHelper
+ # Navigation tabs helper
+
+ # Create a <gl-tabs> container
+ #
+ # Returns a `ul` element with classes that correspond to
+ # the <gl-tabs/> component. Can be populated by
+ # gl_tab_link_to elements.
+ #
+ # See more at: https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-tabs-tab--default
+ def gl_tabs_nav(html_options = {}, &block)
+ gl_tabs_classes = %w[nav gl-tabs-nav]
+
+ html_options = html_options.merge(
+ class: [*html_options[:class], gl_tabs_classes].join(' '),
+ role: 'tablist'
+ )
+
+ content = capture(&block) if block_given?
+ content_tag(:ul, content, html_options)
+ end
+
+ # Create a <gl-tab> link
+ #
+ # When a tab is active it gets highlighted to indicate this is currently viewed tab.
+ # Internally `current_page?` is called to determine if this is the current tab.
+ #
+ # Usage is the same as "link_to", with the following additional options:
+ #
+ # html_options - The html_options hash (default: {})
+ # :item_active - Overrides the default state focing the "active" css classes (optional).
+ #
+ def gl_tab_link_to(name = nil, options = {}, html_options = {}, &block)
+ tab_class = 'nav-item'
+ link_classes = %w[nav-link gl-tab-nav-item]
+ active_link_classes = %w[active gl-tab-nav-item-active gl-tab-nav-item-active-indigo]
+
+ if block_given?
+ # Shift params to skip the omitted "name" param
+ html_options = options
+ options = name
+ end
+
+ html_options = html_options.merge(
+ class: [*html_options[:class], link_classes].join(' ')
+ )
+
+ if gl_tab_link_to_active?(options, html_options)
+ html_options[:class] = [*html_options[:class], active_link_classes].join(' ')
+ end
+
+ html_options = html_options.except(:item_active)
+
+ content_tag(:li, class: tab_class, role: 'presentation') do
+ if block_given?
+ link_to(options, html_options, &block)
+ else
+ link_to(name, options, html_options)
+ end
+ end
+ end
+
# Navigation link helper
#
# Returns an `li` element with an 'active' class if the supplied
@@ -148,4 +209,12 @@ module TabHelper
current_controller?(*controller) || current_action?(*action)
end
end
+
+ def gl_tab_link_to_active?(options, html_options)
+ if html_options.has_key?(:item_active)
+ return html_options[:item_active]
+ end
+
+ current_page?(options)
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 4363480cd18..77fc31c0bff 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -479,6 +479,8 @@ class ApplicationSetting < ApplicationRecord
validates :throttle_unauthenticated_packages_api_period_in_seconds
validates :throttle_unauthenticated_files_api_requests_per_period
validates :throttle_unauthenticated_files_api_period_in_seconds
+ validates :throttle_unauthenticated_deprecated_api_requests_per_period
+ validates :throttle_unauthenticated_deprecated_api_period_in_seconds
validates :throttle_authenticated_api_requests_per_period
validates :throttle_authenticated_api_period_in_seconds
validates :throttle_authenticated_git_lfs_requests_per_period
@@ -489,6 +491,8 @@ class ApplicationSetting < ApplicationRecord
validates :throttle_authenticated_packages_api_period_in_seconds
validates :throttle_authenticated_files_api_requests_per_period
validates :throttle_authenticated_files_api_period_in_seconds
+ validates :throttle_authenticated_deprecated_api_requests_per_period
+ validates :throttle_authenticated_deprecated_api_period_in_seconds
validates :throttle_protected_paths_requests_per_period
validates :throttle_protected_paths_period_in_seconds
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 7859a8d62cb..7bdea36bb8a 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -176,6 +176,9 @@ module ApplicationSettingImplementation
throttle_authenticated_files_api_enabled: false,
throttle_authenticated_files_api_period_in_seconds: 15,
throttle_authenticated_files_api_requests_per_period: 500,
+ throttle_authenticated_deprecated_api_enabled: false,
+ throttle_authenticated_deprecated_api_period_in_seconds: 3600,
+ throttle_authenticated_deprecated_api_requests_per_period: 3600,
throttle_incident_management_notification_enabled: false,
throttle_incident_management_notification_per_period: 3600,
throttle_incident_management_notification_period_in_seconds: 3600,
@@ -194,6 +197,9 @@ module ApplicationSettingImplementation
throttle_unauthenticated_files_api_enabled: false,
throttle_unauthenticated_files_api_period_in_seconds: 15,
throttle_unauthenticated_files_api_requests_per_period: 125,
+ throttle_unauthenticated_deprecated_api_enabled: false,
+ throttle_unauthenticated_deprecated_api_period_in_seconds: 3600,
+ throttle_unauthenticated_deprecated_api_requests_per_period: 1800,
time_tracking_limit_to_hours: false,
two_factor_grace_period: 48,
unique_ips_limit_enabled: false,
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 2d37b6e0385..58e3f3f1136 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -49,6 +49,18 @@
.settings-content
= render partial: 'network_rate_limits', locals: { anchor: 'js-files-limits-settings', setting_fragment: 'files_api' }
+%section.settings.as-deprecated-limits.no-animate#js-deprecated-limits-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Deprecated API rate limits')
+ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure specific limits for deprecated API requests that supersede the general user and IP rate limits.')
+ = link_to _('Which API requests are affected?'), help_page_path('user/admin_area/settings/deprecated_api_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ .settings-content
+ = render partial: 'network_rate_limits', locals: { anchor: 'js-deprecated-limits-settings', setting_fragment: 'deprecated_api' }
+
%section.settings.as-git-lfs-limits.no-animate#js-git-lfs-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'git_lfs_limits_content' } }
.settings-header
%h4
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 5ebfd296e2b..f30e8eb0d54 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,17 +1,12 @@
- page_title _('Projects')
- params[:visibility_level] ||= []
-- active_tab_classes = 'active gl-tab-nav-item-active gl-tab-nav-item-active-indigo'
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
- %ul.nav.gl-tabs-nav.gl-overflow-x-auto.gl-display-flex.gl-flex-grow-1.gl-flex-shrink-1.gl-border-b-0.gl-flex-nowrap.gl-webkit-scrollbar-display-none
- = nav_link(html_options: { class: "nav-item" } ) do
- = link_to _('All'), admin_projects_path, class: "nav-link gl-tab-nav-item #{active_tab_classes if params[:visibility_level].empty?}"
- = nav_link(html_options: { class: "nav-item" } ) do
- = link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE), class: "nav-link gl-tab-nav-item #{active_tab_classes if params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s}"
- = nav_link(html_options: { class: "nav-item" } ) do
- = link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL), class: "nav-link gl-tab-nav-item #{active_tab_classes if params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s}"
- = nav_link(html_options: { class: "nav-item" } ) do
- = link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC), class: "nav-link gl-tab-nav-item #{active_tab_classes if params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s}"
+ = gl_tabs_nav({ class: 'gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-nowrap gl-webkit-scrollbar-display-none' }) do
+ = gl_tab_link_to _('All'), admin_projects_path(visibility_level: nil), { item_active: params[:visibility_level].empty? }
+ = gl_tab_link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ = gl_tab_link_to _('Internal'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ = gl_tab_link_to _('Public'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
.nav-controls
.search-holder
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index f4b1a2853f1..bafb2085589 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -33,16 +33,11 @@
- if can_force_email_confirmation?(@user)
%button.btn.gl-button.btn-info.js-confirm-modal-button{ data: confirm_user_data(@user) }
= _('Confirm user')
-%ul.nav-links.nav.nav-tabs
- = nav_link(path: 'users#show') do
- = link_to _("Account"), admin_user_path(@user)
- = nav_link(path: 'users#projects') do
- = link_to _("Groups and projects"), projects_admin_user_path(@user)
- = nav_link(path: 'users#keys') do
- = link_to _("SSH keys"), keys_admin_user_path(@user)
- = nav_link(controller: :identities) do
- = link_to _("Identities"), admin_user_identities_path(@user)
+= gl_tabs_nav do
+ = gl_tab_link_to _("Account"), admin_user_path(@user)
+ = gl_tab_link_to _("Groups and projects"), projects_admin_user_path(@user)
+ = gl_tab_link_to _("SSH keys"), keys_admin_user_path(@user)
+ = gl_tab_link_to _("Identities"), admin_user_identities_path(@user)
- if impersonation_enabled?
- = nav_link(controller: :impersonation_tokens) do
- = link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user)
+ = gl_tab_link_to _("Impersonation Tokens"), admin_user_impersonation_tokens_path(@user)
.gl-mb-3
diff --git a/db/migrate/20210914145810_add_throttle_deprecated_api_columns.rb b/db/migrate/20210914145810_add_throttle_deprecated_api_columns.rb
new file mode 100644
index 00000000000..6e57429bca8
--- /dev/null
+++ b/db/migrate/20210914145810_add_throttle_deprecated_api_columns.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddThrottleDeprecatedApiColumns < Gitlab::Database::Migration[1.0]
+ def change
+ add_column :application_settings, :throttle_unauthenticated_deprecated_api_requests_per_period, :integer, default: 3600, null: false
+ add_column :application_settings, :throttle_unauthenticated_deprecated_api_period_in_seconds, :integer, default: 3600, null: false
+ add_column :application_settings, :throttle_unauthenticated_deprecated_api_enabled, :boolean, default: false, null: false
+
+ add_column :application_settings, :throttle_authenticated_deprecated_api_requests_per_period, :integer, default: 3600, null: false
+ add_column :application_settings, :throttle_authenticated_deprecated_api_period_in_seconds, :integer, default: 1800, null: false
+ add_column :application_settings, :throttle_authenticated_deprecated_api_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/db/schema_migrations/20210914145810 b/db/schema_migrations/20210914145810
new file mode 100644
index 00000000000..1c2cdc8cbf2
--- /dev/null
+++ b/db/schema_migrations/20210914145810
@@ -0,0 +1 @@
+a30acb6d2a3772be29dfefc7d8cda2f2df94002556fa5de85483b7fca245be86 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 32e7ea0e7a5..8db89f05bdd 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -10338,6 +10338,12 @@ CREATE TABLE application_settings (
sidekiq_job_limiter_compression_threshold_bytes integer DEFAULT 100000 NOT NULL,
sidekiq_job_limiter_limit_bytes integer DEFAULT 0 NOT NULL,
suggest_pipeline_enabled boolean DEFAULT true NOT NULL,
+ throttle_unauthenticated_deprecated_api_requests_per_period integer DEFAULT 1800 NOT NULL,
+ throttle_unauthenticated_deprecated_api_period_in_seconds integer DEFAULT 3600 NOT NULL,
+ throttle_unauthenticated_deprecated_api_enabled boolean DEFAULT false NOT NULL,
+ throttle_authenticated_deprecated_api_requests_per_period integer DEFAULT 3600 NOT NULL,
+ throttle_authenticated_deprecated_api_period_in_seconds integer DEFAULT 3600 NOT NULL,
+ throttle_authenticated_deprecated_api_enabled boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index aeb5c762dda..ea9eb8f33fb 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -102,6 +102,15 @@ This setting limits the request rate on the Packages API per user or IP address.
- **Default rate limit**: Disabled by default.
+### Deprecated API endpoints
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68645) in GitLab 14.4.
+
+This setting limits the request rate on deprecated API endpoints per user or IP address. For more information, read
+[Deprecated API rate limits](../user/admin_area/settings/deprecated_api_rate_limits.md).
+
+- **Default rate limit**: Disabled by default.
+
### Import/Export
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
diff --git a/doc/development/fe_guide/storybook.md b/doc/development/fe_guide/storybook.md
index 15225cc1deb..3ccb13b5bff 100644
--- a/doc/development/fe_guide/storybook.md
+++ b/doc/development/fe_guide/storybook.md
@@ -33,7 +33,7 @@ Stories can be added for any Vue component in the `gitlab` repository.
To add a story:
1. Create a new `.stories.js` file in the same directory as the Vue component.
- The file name should have the same prefix as the Vue component.
+ The filename should have the same prefix as the Vue component.
```txt
vue_shared/
diff --git a/doc/development/graphql_guide/pagination.md b/doc/development/graphql_guide/pagination.md
index a37c47f1b11..1f40a605cfe 100644
--- a/doc/development/graphql_guide/pagination.md
+++ b/doc/development/graphql_guide/pagination.md
@@ -338,9 +338,9 @@ describe 'sorting and pagination' do
let(:ordered_issues) { issues.sort_by(&:weight) }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :WEIGHT_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { ordered_issues.map(&:iid) }
+ let(:sort_param) { :WEIGHT_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { ordered_issues.map(&:iid) }
end
end
end
diff --git a/doc/development/snowplow/index.md b/doc/development/snowplow/index.md
index e8b7d871b77..11525f186c1 100644
--- a/doc/development/snowplow/index.md
+++ b/doc/development/snowplow/index.md
@@ -720,7 +720,7 @@ The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/g
| `geo_longitude` | **{dotted-circle}** | string | An approximate longitude |
| `geo_region` | **{dotted-circle}** | string | Region of IP origin |
| `geo_region_name` | **{dotted-circle}** | string | Region of IP origin |
-| `geo_timezone` | **{dotted-circle}** | string | Timezone of IP origin |
+| `geo_timezone` | **{dotted-circle}** | string | Time zone of IP origin |
| `geo_zipcode` | **{dotted-circle}** | string | Zip (postal) code of IP origin |
| `ip_domain` | **{dotted-circle}** | string | Second level domain name associated with the visitor's IP address |
| `ip_isp` | **{dotted-circle}** | string | Visitor's ISP |
@@ -738,7 +738,7 @@ The [`StandardContext`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/g
| `os_family` | **{dotted-circle}** | string | Operating system family |
| `os_manufacturer` | **{dotted-circle}** | string | Manufacturers of operating system |
| `os_name` | **{dotted-circle}** | string | Name of operating system |
-| `os_timezone` | **{dotted-circle}** | string | Client operating system timezone |
+| `os_timezone` | **{dotted-circle}** | string | Client operating system time zone |
| `page_referrer` | **{dotted-circle}** | string | Referrer URL |
| `page_title` | **{dotted-circle}** | string | Page title |
| `page_url` | **{dotted-circle}** | string | Page URL |
diff --git a/doc/development/stage_group_dashboards.md b/doc/development/stage_group_dashboards.md
index 7c518e9b6ca..5c1d7b17f0c 100644
--- a/doc/development/stage_group_dashboards.md
+++ b/doc/development/stage_group_dashboards.md
@@ -120,7 +120,7 @@ Inside a stage group dashboard, there are some notable components. Let's take th
![Default time filter](img/stage_group_dashboards_time_filter.png)
-- By default, all the times are in UTC timezone. [We use UTC when communicating in Engineering](https://about.gitlab.com/handbook/communication/#writing-style-guidelines).
+- By default, all the times are in UTC time zone. [We use UTC when communicating in Engineering](https://about.gitlab.com/handbook/communication/#writing-style-guidelines).
- All metrics recorded in the GitLab production system have [1-year retention](https://gitlab.com/gitlab-cookbooks/gitlab-prometheus/-/blob/31526b03fef823e2f9b3cda7c75dcd28a12418a3/attributes/prometheus.rb#L40).
- Alternatively, you can zoom in or filter the time range directly on a graph. See the [Grafana Time Range Controls](https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/) documentation for more information.
diff --git a/doc/development/testing_guide/end_to_end/best_practices.md b/doc/development/testing_guide/end_to_end/best_practices.md
index a3caa8bf2b3..baa4cddc306 100644
--- a/doc/development/testing_guide/end_to_end/best_practices.md
+++ b/doc/development/testing_guide/end_to_end/best_practices.md
@@ -333,11 +333,11 @@ after(:all) do
end
```
-## Tag tests that require Administrator access
+## Tag tests that require the Administrator role
-We don't run tests that require Administrator access against our Production environments.
+We don't run tests that require the Administrator role against our Production environments.
-When you add a new test that requires Administrator access, apply the RSpec metadata `:requires_admin` so that the test will not be included in the test suites executed against Production and other environments on which we don't want to run those tests.
+When you add a new test that requires the Administrator role, apply the RSpec metadata `:requires_admin` so that the test will not be included in the test suites executed against Production and other environments on which we don't want to run those tests.
When running tests locally or configuring a pipeline, the environment variable `QA_CAN_TEST_ADMIN_FEATURES` can be set to `false` to skip tests that have the `:requires_admin` tag.
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index a5249cb162f..03173fdea94 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -36,6 +36,7 @@ These are rate limits you can set in the Admin Area of your instance:
- [Package registry rate limits](../user/admin_area/settings/package_registry_rate_limits.md)
- [Git LFS rate limits](../user/admin_area/settings/git_lfs_rate_limits.md)
- [Files API rate limits](../user/admin_area/settings/files_api_rate_limits.md)
+- [Deprecated API rate limits](../user/admin_area/settings/deprecated_api_rate_limits.md)
## Non-configurable limits
diff --git a/doc/user/admin_area/settings/deprecated_api_rate_limits.md b/doc/user/admin_area/settings/deprecated_api_rate_limits.md
new file mode 100644
index 00000000000..a2edb6da86e
--- /dev/null
+++ b/doc/user/admin_area/settings/deprecated_api_rate_limits.md
@@ -0,0 +1,53 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+type: reference
+---
+
+# Deprecated API rate limits **(FREE SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68645) in GitLab 14.4.
+
+Deprecated API endpoints are those which have been replaced with alternative
+functionality, but cannot be removed without breaking backward compatibility.
+Setting a restrictive rate limit on these endpoints can encourage users to
+switch to the alternatives.
+
+## Deprecated API endpoints
+
+Not all deprecated API endpoints are included in this rate limit - just those
+that might have a performance impact:
+
+- [`GET /groups/:id`](../../../api/groups.md#details-of-a-group) **without** the `with_projects=0` query parameter.
+
+## Define Deprecated API rate limits
+
+Rate limits for deprecated API endpoints are disabled by default. When enabled, they supersede
+the general user and IP rate limits for requests to deprecated endpoints. You can keep any general user
+and IP rate limits already in place, and increase or decrease the rate limits
+for deprecated API endpoints. No other new features are provided by this override.
+
+Prerequisites:
+
+- You must have the Administrator role for your instance.
+
+To override the general user and IP rate limits for requests to deprecated API endpoints:
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Settings > Network**.
+1. Expand **Deprecated API Rate Limits**.
+1. Select the check boxes for the types of rate limits you want to enable:
+ - **Unauthenticated API request rate limit**
+ - **Authenticated API request rate limit**
+1. _If you enabled unauthenticated API request rate limits:_
+ 1. Select the **Maximum unauthenticated API requests per period per IP**.
+ 1. Select the **Unauthenticated API rate limit period in seconds**.
+1. _If you enabled authenticated API request rate limits:_
+ 1. Select the **Maximum authenticated API requests per period per user**.
+ 1. Select the **Authenticated API rate limit period in seconds**.
+
+## Resources
+
+- [Rate limits](../../../security/rate_limits.md)
+- [User and IP rate limits](user_and_ip_rate_limits.md)
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index f0bd378413a..688734849e5 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -99,6 +99,7 @@ To access the default page for Admin Area settings:
| [Package Registry Rate Limits](package_registry_rate_limits.md) | Configure specific limits for Packages API requests that supersede the user and IP rate limits. |
| [Git LFS Rate Limits](git_lfs_rate_limits.md) | Configure specific limits for Git LFS requests that supersede the user and IP rate limits. |
| [Files API Rate Limits](files_api_rate_limits.md) | Configure specific limits for Files API requests that supersede the user and IP rate limits. |
+| [Deprecated API Rate Limits](deprecated_api_rate_limits.md) | Configure specific limits for deprecated API requests that supersede the user and IP rate limits. |
| [Outbound requests](../../../security/webhooks.md) | Allow requests to the local network from hooks and services. |
| [Protected Paths](protected_paths.md) | Configure paths to be protected by Rack Attack. |
| [Incident Management](../../../operations/incident_management/index.md) Limits | Limit the number of inbound alerts that can be sent to a project. |
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
index c138096768e..ac6fe29da28 100644
--- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -191,6 +191,8 @@ The possible names are:
- `throttle_authenticated_git_lfs`
- `throttle_unauthenticated_files_api`
- `throttle_authenticated_files_api`
+- `throttle_unauthenticated_deprecated_api`
+- `throttle_authenticated_deprecated_api`
For example, to try out throttles for all authenticated requests to
non-protected paths can be done by setting
diff --git a/doc/user/clusters/agent/repository.md b/doc/user/clusters/agent/repository.md
index 51efed4f567..cbb27a3f343 100644
--- a/doc/user/clusters/agent/repository.md
+++ b/doc/user/clusters/agent/repository.md
@@ -173,8 +173,8 @@ An Agent can only authorize groups in the same group hierarchy as the Agent's co
To authorize a group:
-1. Edit your `.config.yaml` file under the `.gitlab/agents/<agent name>` directory.
-1. Add the `ci_access` attribute.
+1. Edit your `config.yaml` file under the `.gitlab/agents/<agent name>` directory.
+1. Add the `ci_access` root attribute.
1. Add the `groups` attribute into `ci_access`.
1. Add the group `id` into `groups`, identifying the authorized group through its path.
diff --git a/lib/api/base.rb b/lib/api/base.rb
index fef82b01cd4..0c6e16930cf 100644
--- a/lib/api/base.rb
+++ b/lib/api/base.rb
@@ -2,13 +2,17 @@
module API
class Base < Grape::API::Instance # rubocop:disable API/Base
- include ::Gitlab::WithFeatureCategory
+ include ::Gitlab::EndpointAttributes
class << self
def feature_category_for_app(app)
feature_category_for_action(path_for_app(app))
end
+ def target_duration_for_app(app)
+ target_duration_for_action(path_for_app(app))
+ end
+
def path_for_app(app)
normalize_path(app.namespace, app.options[:path].first)
end
@@ -18,8 +22,13 @@ module API
end
def route(methods, paths = ['/'], route_options = {}, &block)
+ actions = Array(paths).map { |path| normalize_path(namespace, path) }
if category = route_options.delete(:feature_category)
- feature_category(category, Array(paths).map { |path| normalize_path(namespace, path) })
+ feature_category(category, actions)
+ end
+
+ if target = route_options.delete(:target_duration)
+ target_duration(target, actions)
end
super
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index d740c626557..6efb3cb7817 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -164,7 +164,7 @@ module API
#
# Check whether an SSH key is known to GitLab
#
- get '/authorized_keys', feature_category: :source_code_management do
+ get '/authorized_keys', feature_category: :source_code_management, target_duration: :very_fast do
fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256
key = Key.find_by_fingerprint_sha256(fingerprint)
diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb
index 37fe540cde1..f0d64c433c4 100644
--- a/lib/api/unleash.rb
+++ b/lib/api/unleash.rb
@@ -30,7 +30,7 @@ module API
end
desc 'Get a list of features'
- get 'client/features' do
+ get 'client/features', target_duration: :fast do
present :version, 1
present :features, feature_flags, with: ::API::Entities::UnleashFeature
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index e3271b8b9b2..c82bcc53cb2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -787,7 +787,7 @@ module API
use :pagination
optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
end
- get feature_category :authentication_and_authorization do
+ get feature_category: :authentication_and_authorization do
present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken
end
diff --git a/lib/gitlab/endpoint_attributes.rb b/lib/gitlab/endpoint_attributes.rb
new file mode 100644
index 00000000000..11e41863cb8
--- /dev/null
+++ b/lib/gitlab/endpoint_attributes.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module EndpointAttributes
+ extend ActiveSupport::Concern
+ include Gitlab::ClassAttributes
+
+ DEFAULT_TARGET_DURATION = Config::TARGET_DURATIONS.fetch(:medium)
+
+ class_methods do
+ def feature_category(category, actions = [])
+ endpoint_attributes.set(actions, feature_category: category)
+ end
+
+ def feature_category_for_action(action)
+ category = endpoint_attributes.attribute_for_action(action, :feature_category)
+ category || superclass_feature_category_for_action(action)
+ end
+
+ def target_duration(duration, actions = [])
+ endpoint_attributes.set(actions, target_duration: duration)
+ end
+
+ def target_duration_for_action(action)
+ duration = endpoint_attributes.attribute_for_action(action, :target_duration)
+ duration || superclass_target_duration_for_action(action) || DEFAULT_TARGET_DURATION
+ end
+
+ private
+
+ def endpoint_attributes
+ class_attributes[:endpoint_attributes_config] ||= Config.new
+ end
+
+ def superclass_feature_category_for_action(action)
+ return unless superclass.respond_to?(:feature_category_for_action)
+
+ superclass.feature_category_for_action(action)
+ end
+
+ def superclass_target_duration_for_action(action)
+ return unless superclass.respond_to?(:target_duration_for_action)
+
+ superclass.target_duration_for_action(action)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/endpoint_attributes/config.rb b/lib/gitlab/endpoint_attributes/config.rb
new file mode 100644
index 00000000000..f36f9671929
--- /dev/null
+++ b/lib/gitlab/endpoint_attributes/config.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module EndpointAttributes
+ class Config
+ Duration = Struct.new(:name, :duration)
+ TARGET_DURATIONS = [
+ Duration.new(:very_fast, 0.25),
+ Duration.new(:fast, 0.5),
+ Duration.new(:medium, 1),
+ Duration.new(:slow, 5)
+ ].index_by(&:name).freeze
+ SUPPORTED_ATTRIBUTES = %i[feature_category target_duration].freeze
+
+ def initialize
+ @default_attributes = {}
+ @action_attributes = {}
+ end
+
+ def defined_actions
+ @action_attributes.keys
+ end
+
+ def set(actions, attributes)
+ sanitize_attributes!(attributes)
+
+ if actions.empty?
+ conflicted = conflicted_attributes(attributes, @default_attributes)
+ raise ArgumentError, "Attributes already defined: #{conflicted.join(", ")}" if conflicted.present?
+
+ @default_attributes.merge!(attributes)
+ else
+ set_attributes_for_actions(actions, attributes)
+ end
+
+ nil
+ end
+
+ def attribute_for_action(action, attribute_name)
+ value = @action_attributes.dig(action.to_s, attribute_name) || @default_attributes[attribute_name]
+ # Translate target duration to a representative struct
+ value = TARGET_DURATIONS[value] if attribute_name == :target_duration
+ value
+ end
+
+ private
+
+ def sanitize_attributes!(attributes)
+ unsupported_attributes = (attributes.keys - SUPPORTED_ATTRIBUTES).present?
+ raise ArgumentError, "Attributes not supported: #{unsupported_attributes.join(", ")}" if unsupported_attributes
+
+ if attributes[:target_duration].present? && !TARGET_DURATIONS.key?(attributes[:target_duration])
+ raise ArgumentError, "Target duration not supported: #{attributes[:target_duration]}"
+ end
+ end
+
+ def set_attributes_for_actions(actions, attributes)
+ conflicted = conflicted_attributes(attributes, @default_attributes)
+ if conflicted.present?
+ raise ArgumentError, "#{conflicted.join(", ")} are already defined for all actions, but re-defined for #{actions.join(", ")}"
+ end
+
+ actions.each do |action|
+ action = action.to_s
+ if @action_attributes[action].blank?
+ @action_attributes[action] = attributes.dup
+ else
+ conflicted = conflicted_attributes(attributes, @action_attributes[action])
+ raise ArgumentError, "Attributes re-defined for action #{action}: #{conflicted.join(", ")}" if conflicted.present?
+
+ @action_attributes[action].merge!(attributes)
+ end
+ end
+ end
+
+ def conflicted_attributes(attributes, existing_attributes)
+ attributes.keys.filter { |attr| existing_attributes[attr].present? && existing_attributes[attr] != attributes[attr] }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb
index 071645c2c14..5061202d2ec 100644
--- a/lib/gitlab/metrics/requests_rack_middleware.rb
+++ b/lib/gitlab/metrics/requests_rack_middleware.rb
@@ -79,7 +79,7 @@ module Gitlab
if !health_endpoint && ::Gitlab::Metrics.record_duration_for_status?(status)
self.class.http_request_duration_seconds.observe({ method: method }, elapsed)
- record_apdex_if_needed(elapsed)
+ record_apdex_if_needed(env, elapsed)
end
[status, headers, body]
@@ -113,14 +113,12 @@ module Gitlab
::Gitlab::ApplicationContext.current_context_attribute(:caller_id)
end
- def record_apdex_if_needed(elapsed)
+ def record_apdex_if_needed(env, elapsed)
return unless Gitlab::Metrics::RailsSlis.request_apdex_counters_enabled?
Gitlab::Metrics::RailsSlis.request_apdex.increment(
labels: labels_from_context,
- # hardcoded 1s here will be replaced by a per-endpoint value.
- # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1223
- success: elapsed < 1
+ success: satisfactory?(env, elapsed)
)
end
@@ -130,6 +128,19 @@ module Gitlab
endpoint_id: endpoint_id.presence || ENDPOINT_MISSING
}
end
+
+ def satisfactory?(env, elapsed)
+ target =
+ if env['api.endpoint'].present?
+ env['api.endpoint'].options[:for].try(:target_duration_for_app, env['api.endpoint'])
+ elsif env['action_controller.instance'].present? && env['action_controller.instance'].respond_to?(:target_duration)
+ env['action_controller.instance'].target_duration
+ end
+
+ target ||= Gitlab::EndpointAttributes::DEFAULT_TARGET_DURATION
+
+ elapsed < target.duration
+ end
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/rack_attack.rb b/lib/gitlab/metrics/subscribers/rack_attack.rb
index ebd0d1634e7..d86c0f83c6c 100644
--- a/lib/gitlab/metrics/subscribers/rack_attack.rb
+++ b/lib/gitlab/metrics/subscribers/rack_attack.rb
@@ -22,7 +22,8 @@ module Gitlab
:throttle_authenticated_protected_paths_web,
:throttle_authenticated_packages_api,
:throttle_authenticated_git_lfs,
- :throttle_authenticated_files_api
+ :throttle_authenticated_files_api,
+ :throttle_authenticated_deprecated_api
].freeze
PAYLOAD_KEYS = [
diff --git a/lib/gitlab/quick_actions/relate_actions.rb b/lib/gitlab/quick_actions/relate_actions.rb
index 95f71214667..1de23523f01 100644
--- a/lib/gitlab/quick_actions/relate_actions.rb
+++ b/lib/gitlab/quick_actions/relate_actions.rb
@@ -17,11 +17,17 @@ module Gitlab
params '#issue'
types Issue
condition do
- quick_action_target.persisted? &&
- current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end
- command :relate do |related_param|
- IssueLinks::CreateService.new(quick_action_target, current_user, { issuable_references: [related_param] }).execute
+ command :relate do |related_reference|
+ service = IssueLinks::CreateService.new(quick_action_target, current_user, { issuable_references: [related_reference] })
+ create_issue_link = proc { service.execute }
+
+ if quick_action_target.persisted?
+ create_issue_link.call
+ else
+ quick_action_target.run_after_commit(&create_issue_link)
+ end
end
end
end
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index 099174842d0..dbc77c9f9d7 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -4,6 +4,7 @@ module Gitlab
module RackAttack
module Request
FILES_PATH_REGEX = %r{^/api/v\d+/projects/[^/]+/repository/files/.+}.freeze
+ GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze
def unauthenticated?
!(authenticated_user_id([:api, :rss, :ics]) || authenticated_runner_id)
@@ -71,6 +72,7 @@ module Gitlab
!should_be_skipped? &&
!throttle_unauthenticated_packages_api? &&
!throttle_unauthenticated_files_api? &&
+ !throttle_unauthenticated_deprecated_api? &&
Gitlab::Throttle.settings.throttle_unauthenticated_api_enabled &&
unauthenticated?
end
@@ -87,6 +89,7 @@ module Gitlab
api_request? &&
!throttle_authenticated_packages_api? &&
!throttle_authenticated_files_api? &&
+ !throttle_authenticated_deprecated_api? &&
Gitlab::Throttle.settings.throttle_authenticated_api_enabled
end
@@ -147,6 +150,17 @@ module Gitlab
Gitlab::Throttle.settings.throttle_authenticated_files_api_enabled
end
+ def throttle_unauthenticated_deprecated_api?
+ deprecated_api_request? &&
+ Gitlab::Throttle.settings.throttle_unauthenticated_deprecated_api_enabled &&
+ unauthenticated?
+ end
+
+ def throttle_authenticated_deprecated_api?
+ deprecated_api_request? &&
+ Gitlab::Throttle.settings.throttle_authenticated_deprecated_api_enabled
+ end
+
private
def authenticated_user_id(request_formats)
@@ -176,6 +190,15 @@ module Gitlab
def files_api_path?
path =~ FILES_PATH_REGEX
end
+
+ def deprecated_api_request?
+ # The projects member of the groups endpoint is deprecated. If left
+ # unspecified, with_projects defaults to true
+ with_projects = params['with_projects']
+ with_projects = true if with_projects.blank?
+
+ path =~ GROUP_PATH_REGEX && Gitlab::Utils.to_boolean(with_projects)
+ end
end
end
end
diff --git a/lib/gitlab/request_endpoints.rb b/lib/gitlab/request_endpoints.rb
index 04fdffe3d28..157c0f91e65 100644
--- a/lib/gitlab/request_endpoints.rb
+++ b/lib/gitlab/request_endpoints.rb
@@ -21,7 +21,7 @@ module Gitlab
next if route_info[:controller].blank? || route_info[:action].blank?
controller = constantize_controller(route_info[:controller])
- next unless controller&.include?(::Gitlab::WithFeatureCategory)
+ next unless controller&.include?(::Gitlab::EndpointAttributes)
next if controller == ApplicationController
next if controller == Devise::UnlocksController
diff --git a/lib/gitlab/throttle.rb b/lib/gitlab/throttle.rb
index 622dc7d9ed0..384953533b5 100644
--- a/lib/gitlab/throttle.rb
+++ b/lib/gitlab/throttle.rb
@@ -7,7 +7,7 @@ module Gitlab
# Each of these settings follows the same pattern of specifying separate
# authenticated and unauthenticated rates via settings. New throttles should
# ideally be regular as well.
- REGULAR_THROTTLES = [:api, :packages_api, :files_api].freeze
+ REGULAR_THROTTLES = [:api, :packages_api, :files_api, :deprecated_api].freeze
def self.settings
Gitlab::CurrentSettings.current_application_settings
diff --git a/lib/gitlab/with_feature_category.rb b/lib/gitlab/with_feature_category.rb
deleted file mode 100644
index 65d21daf78a..00000000000
--- a/lib/gitlab/with_feature_category.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module WithFeatureCategory
- extend ActiveSupport::Concern
- include Gitlab::ClassAttributes
-
- class_methods do
- def feature_category(category, actions = [])
- feature_category_configuration[category] ||= []
- feature_category_configuration[category] += actions.map(&:to_s)
-
- validate_config!(feature_category_configuration)
- end
-
- def feature_category_for_action(action)
- category_config = feature_category_configuration.find do |_, actions|
- actions.empty? || actions.include?(action)
- end
-
- category_config&.first || superclass_feature_category_for_action(action)
- end
-
- private
-
- def validate_config!(config)
- empty = config.find { |_, actions| actions.empty? }
- duplicate_actions = config.values.map(&:uniq).flatten.group_by(&:itself).select { |_, v| v.count > 1 }.keys
-
- if config.length > 1 && empty
- raise ArgumentError, "#{empty.first} is defined for all actions, but other categories are set"
- end
-
- if duplicate_actions.any?
- raise ArgumentError, "Actions have multiple feature categories: #{duplicate_actions.join(', ')}"
- end
- end
-
- def feature_category_configuration
- class_attributes[:feature_category_config] ||= {}
- end
-
- def superclass_feature_category_for_action(action)
- return unless superclass.respond_to?(:feature_category_for_action)
-
- superclass.feature_category_for_action(action)
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6f10030b56c..8ee6b61ad52 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4110,6 +4110,11 @@ msgstr ""
msgid "Apply"
msgstr ""
+msgid "Apply %d suggestion"
+msgid_plural "Apply %d suggestions"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Apply a label"
msgstr ""
@@ -4119,9 +4124,6 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
-msgid "Apply suggestions"
-msgstr ""
-
msgid "Apply template"
msgstr ""
@@ -8608,6 +8610,9 @@ msgstr ""
msgid "Configure specific limits for Git LFS requests that supersede the general user and IP rate limits."
msgstr ""
+msgid "Configure specific limits for deprecated API requests that supersede the general user and IP rate limits."
+msgstr ""
+
msgid "Configure the %{link} integration."
msgstr ""
@@ -11418,6 +11423,9 @@ msgstr ""
msgid "Deployment|success"
msgstr ""
+msgid "Deprecated API rate limits"
+msgstr ""
+
msgid "Deprioritize label"
msgstr ""
@@ -18215,6 +18223,9 @@ msgstr ""
msgid "Integrations|Create new issue in Jira"
msgstr ""
+msgid "Integrations|Create new issue in ZenTao"
+msgstr ""
+
msgid "Integrations|Default settings are inherited from the group level."
msgstr ""
@@ -18308,6 +18319,9 @@ msgstr ""
msgid "Integrations|Search Jira issues"
msgstr ""
+msgid "Integrations|Search ZenTao issues"
+msgstr ""
+
msgid "Integrations|Send notifications about project events to Unify Circuit."
msgstr ""
@@ -18353,6 +18367,9 @@ msgstr ""
msgid "Integrations|You've activated every integration 🎉"
msgstr ""
+msgid "Integrations|ZenTao issues display here when you create issues in your project in ZenTao."
+msgstr ""
+
msgid "Interactive mode"
msgstr ""
@@ -38135,6 +38152,9 @@ msgstr ""
msgid "When:"
msgstr ""
+msgid "Which API requests are affected?"
+msgstr ""
+
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 7de99a11cf6..22bcad5994a 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -93,7 +93,6 @@ module QA
end
view 'app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue' do
- element :apply_suggestions_batch_button
element :add_suggestion_batch_button
end
@@ -353,10 +352,6 @@ module QA
all_elements(:add_suggestion_batch_button, minimum: 1).first.click
end
- def apply_suggestions_batch
- all_elements(:apply_suggestions_batch_button, minimum: 1).first.click
- end
-
def cherry_pick!
click_element(:cherry_pick_button, Page::Component::CommitModal)
click_element(:submit_commit_button)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
index 5cebbb32ade..20d312b43c0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb
@@ -50,7 +50,7 @@ module QA
Page::MergeRequest::Show.perform do |merge_request|
merge_request.click_diffs_tab
4.times { merge_request.add_suggestion_to_batch }
- merge_request.apply_suggestions_batch
+ merge_request.apply_suggestion_with_message("Custom commit message")
expect(merge_request).to have_css('.badge-success', text: "Applied", count: 4)
end
diff --git a/spec/controllers/every_controller_spec.rb b/spec/controllers/every_controller_spec.rb
index 7d9d1d158b2..54bbb5dbd01 100644
--- a/spec/controllers/every_controller_spec.rb
+++ b/spec/controllers/every_controller_spec.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
-
RSpec.describe "Every controller" do
context "feature categories" do
let_it_be(:feature_categories) do
@@ -65,9 +64,6 @@ RSpec.describe "Every controller" do
end
def actions_defined_in_feature_category_config(controller)
- controller.send(:class_attributes)[:feature_category_config]
- .values
- .flatten
- .map(&:to_s)
+ controller.send(:class_attributes)[:endpoint_attributes_config].defined_actions
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 310856cc3cb..42f3c2b7872 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -646,6 +646,13 @@ RSpec.describe 'Admin updates settings' do
include_examples 'regular throttle rate limit settings'
end
+
+ context 'Deprecated API rate limits' do
+ let(:selector) { 'as-deprecated-limits' }
+ let(:fragment) { :deprecated_api }
+
+ include_examples 'regular throttle rate limit settings'
+ end
end
context 'Preferences page' do
diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
index dbc88d0cce2..690a292937a 100644
--- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
+++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb
@@ -159,7 +159,12 @@ RSpec.describe 'User comments on a diff', :js do
wait_for_requests
expect(page).to have_content('Remove from batch')
- expect(page).to have_content("Apply suggestions #{index + 1}")
+
+ if index < 1
+ expect(page).to have_content("Apply suggestion")
+ else
+ expect(page).to have_content("Apply #{index + 1} suggestions")
+ end
end
end
@@ -167,13 +172,12 @@ RSpec.describe 'User comments on a diff', :js do
click_button('Remove from batch')
wait_for_requests
- expect(page).to have_content('Apply suggestion')
expect(page).to have_content('Add suggestion to batch')
end
page.within("[id='#{files[1][:hash]}']") do
expect(page).to have_content('Remove from batch')
- expect(page).to have_content('Apply suggestions 1')
+ expect(page).to have_content('Apply suggestion')
end
end
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index 54e816d3d13..cc621dfd9f8 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -19,13 +19,15 @@ RSpec.describe 'Projects > Files > User uploads files' do
wait_for_requests
end
- include_examples 'it uploads and commits a new text file'
+ [true, false].each do |value|
+ include_examples 'it uploads and commits a new text file', drop: value
- include_examples 'it uploads and commits a new image file'
+ include_examples 'it uploads and commits a new image file', drop: value
- include_examples 'it uploads and commits a new pdf file'
+ include_examples 'it uploads and commits a new pdf file', drop: value
- include_examples 'it uploads a file to a sub-directory'
+ include_examples 'it uploads a file to a sub-directory', drop: value
+ end
end
context 'when a user does not have write access' do
@@ -35,6 +37,8 @@ RSpec.describe 'Projects > Files > User uploads files' do
visit(project_tree_path(project2))
end
- include_examples 'it uploads and commits a new file to a forked project'
+ [true, false].each do |value|
+ include_examples 'it uploads and commits a new file to a forked project', drop: value
+ end
end
end
diff --git a/spec/features/projects/show/user_uploads_files_spec.rb b/spec/features/projects/show/user_uploads_files_spec.rb
index eb230082bfa..51e41397439 100644
--- a/spec/features/projects/show/user_uploads_files_spec.rb
+++ b/spec/features/projects/show/user_uploads_files_spec.rb
@@ -21,13 +21,15 @@ RSpec.describe 'Projects > Show > User uploads files' do
wait_for_requests
end
- include_examples 'it uploads and commits a new text file'
+ [true, false].each do |value|
+ include_examples 'it uploads and commits a new text file', drop: value
- include_examples 'it uploads and commits a new image file'
+ include_examples 'it uploads and commits a new image file', drop: value
- include_examples 'it uploads and commits a new pdf file'
+ include_examples 'it uploads and commits a new pdf file', drop: value
- include_examples 'it uploads a file to a sub-directory'
+ include_examples 'it uploads a file to a sub-directory', drop: value
+ end
end
context 'when a user does not have write access' do
@@ -37,7 +39,9 @@ RSpec.describe 'Projects > Show > User uploads files' do
visit(project_path(project2))
end
- include_examples 'it uploads and commits a new file to a forked project'
+ [true, false].each do |value|
+ include_examples 'it uploads and commits a new file to a forked project', drop: value
+ end
end
context 'when in the empty_repo_upload experiment' do
@@ -50,13 +54,17 @@ RSpec.describe 'Projects > Show > User uploads files' do
context 'with an empty repo' do
let(:project) { create(:project, :empty_repo, creator: user) }
- include_examples 'uploads and commits a new text file via "upload file" button'
+ [true, false].each do |value|
+ include_examples 'uploads and commits a new text file via "upload file" button', drop: value
+ end
end
context 'with a nonempty repo' do
let(:project) { create(:project, :repository, creator: user) }
- include_examples 'uploads and commits a new text file via "upload file" button'
+ [true, false].each do |value|
+ include_examples 'uploads and commits a new text file via "upload file" button', drop: value
+ end
end
end
end
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 5a7bfcd9caf..9a20fb1bae6 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -1,7 +1,5 @@
-import DropdownUtils from '~/filtered_search/dropdown_utils';
-// TODO: Moving this line up throws an error about `FilteredSearchDropdown`
-// being undefined in test. See gitlab-org/gitlab#321476 for more info.
import DropdownUser from '~/filtered_search/dropdown_user';
+import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer';
import IssuableFilteredTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index ba2450b56c9..e5fa4a4bf7f 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
expect(findHelpButton().exists()).toBe(true);
});
- it('renders apply suggestion and add to batch buttons', () => {
+ it('renders add to batch button when more than 1 suggestion', () => {
createComponent({
suggestionsCount: 2,
});
@@ -68,8 +68,7 @@ describe('Suggestion Diff component', () => {
const applyBtn = findApplyButton();
const addToBatchBtn = findAddToBatchButton();
- expect(applyBtn.exists()).toBe(true);
- expect(applyBtn.html().includes('Apply suggestion')).toBe(true);
+ expect(applyBtn.exists()).toBe(false);
expect(addToBatchBtn.exists()).toBe(true);
expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true);
@@ -85,7 +84,7 @@ describe('Suggestion Diff component', () => {
describe('when apply suggestion is clicked', () => {
beforeEach(() => {
- createComponent();
+ createComponent({ batchSuggestionsCount: 0 });
findApplyButton().vm.$emit('apply');
});
@@ -140,11 +139,11 @@ describe('Suggestion Diff component', () => {
describe('apply suggestions is clicked', () => {
it('emits applyBatch', () => {
- createComponent({ isBatched: true });
+ createComponent({ isBatched: true, batchSuggestionsCount: 2 });
- findApplyBatchButton().vm.$emit('click');
+ findApplyButton().vm.$emit('apply');
- expect(wrapper.emitted().applyBatch).toEqual([[]]);
+ expect(wrapper.emitted().applyBatch).toEqual([[undefined]]);
});
});
@@ -155,23 +154,24 @@ describe('Suggestion Diff component', () => {
isBatched: true,
});
- const applyBatchBtn = findApplyBatchButton();
+ const applyBatchBtn = findApplyButton();
const removeFromBatchBtn = findRemoveFromBatchButton();
expect(removeFromBatchBtn.exists()).toBe(true);
expect(removeFromBatchBtn.html().includes('Remove from batch')).toBe(true);
expect(applyBatchBtn.exists()).toBe(true);
- expect(applyBatchBtn.html().includes('Apply suggestions')).toBe(true);
+ expect(applyBatchBtn.html().includes('Apply suggestion')).toBe(true);
expect(applyBatchBtn.html().includes(String('9'))).toBe(true);
});
it('hides add to batch and apply buttons', () => {
createComponent({
isBatched: true,
+ batchSuggestionsCount: 9,
});
- expect(findApplyButton().exists()).toBe(false);
+ expect(findApplyButton().exists()).toBe(true);
expect(findAddToBatchButton().exists()).toBe(false);
});
@@ -215,9 +215,8 @@ describe('Suggestion Diff component', () => {
});
it('disables apply suggestion and hides add to batch button', () => {
- expect(findApplyButton().exists()).toBe(true);
+ expect(findApplyButton().exists()).toBe(false);
expect(findAddToBatchButton().exists()).toBe(false);
- expect(findApplyButton().attributes('disabled')).toBe('true');
});
});
@@ -225,20 +224,11 @@ describe('Suggestion Diff component', () => {
const findTooltip = () => getBinding(findApplyButton().element, 'gl-tooltip');
it('renders correct tooltip message when button is applicable', () => {
- createComponent();
+ createComponent({ batchSuggestionsCount: 0 });
const tooltip = findTooltip();
expect(tooltip.modifiers.viewport).toBe(true);
expect(tooltip.value).toBe('This also resolves this thread');
});
-
- it('renders the inapplicable reason in the tooltip when button is not applicable', () => {
- const inapplicableReason = 'lorem';
- createComponent({ canApply: false, inapplicableReason });
- const tooltip = findTooltip();
-
- expect(tooltip.modifiers.viewport).toBe(true);
- expect(tooltip.value).toBe(inapplicableReason);
- });
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
index 5bd6bda2d2c..af27e953776 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -77,7 +77,7 @@ describe('Suggestion Diff component', () => {
it.each`
event | childArgs | args
${'apply'} | ${['test-event']} | ${[{ callback: 'test-event', suggestionId }]}
- ${'applyBatch'} | ${[]} | ${[]}
+ ${'applyBatch'} | ${['test-event']} | ${['test-event']}
${'addToBatch'} | ${[]} | ${[suggestionId]}
${'removeFromBatch'} | ${[]} | ${[suggestionId]}
`('emits $event event on sugestion diff header $event', ({ event, childArgs, args }) => {
diff --git a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
index af4fa462cbf..0f1e118d44c 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
+++ b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
@@ -45,6 +45,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
>
<div
class="mw-50 gl-text-center"
+ style="display: none;"
>
<h3
class=""
@@ -61,7 +62,6 @@ exports[`Upload dropzone component correctly overrides description and drop mess
<div
class="mw-50 gl-text-center"
- style="display: none;"
>
<h3
class=""
@@ -146,7 +146,6 @@ exports[`Upload dropzone component when dragging renders correct template when d
<div
class="mw-50 gl-text-center"
- style=""
>
<h3
class=""
@@ -231,7 +230,6 @@ exports[`Upload dropzone component when dragging renders correct template when d
<div
class="mw-50 gl-text-center"
- style=""
>
<h3
class=""
@@ -299,6 +297,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<div
class="mw-50 gl-text-center"
+ style=""
>
<h3
class=""
@@ -383,6 +382,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<div
class="mw-50 gl-text-center"
+ style=""
>
<h3
class=""
@@ -467,6 +467,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<div
class="mw-50 gl-text-center"
+ style=""
>
<h3
class=""
@@ -551,6 +552,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
>
<div
class="mw-50 gl-text-center"
+ style="display: none;"
>
<h3
class=""
@@ -567,7 +569,6 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
<div
class="mw-50 gl-text-center"
- style="display: none;"
>
<h3
class=""
@@ -603,6 +604,7 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot
>
<div
class="mw-50 gl-text-center"
+ style="display: none;"
>
<h3
class=""
@@ -619,7 +621,6 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot
<div
class="mw-50 gl-text-center"
- style="display: none;"
>
<h3
class=""
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index bd8a8fa174a..346bfc7850c 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -5,6 +5,60 @@ require 'spec_helper'
RSpec.describe TabHelper do
include ApplicationHelper
+ describe 'gl_tabs_nav' do
+ it 'creates a tabs navigation' do
+ expect(gl_tabs_nav).to match(%r{<ul class=".*" role="tablist"><\/ul>})
+ end
+
+ it 'captures block output' do
+ expect(gl_tabs_nav { "block content" }).to match(/block content/)
+ end
+
+ it 'adds styles classes' do
+ expect(gl_tabs_nav).to match(/class="nav gl-tabs-nav"/)
+ end
+
+ it 'adds custom class' do
+ expect(gl_tabs_nav(class: 'my-class' )).to match(/class=".*my-class.*"/)
+ end
+ end
+
+ describe 'gl_tab_link_to' do
+ before do
+ allow(self).to receive(:current_page?).and_return(false)
+ end
+
+ it 'creates a tab' do
+ expect(gl_tab_link_to('Link', '/url')).to eq('<li class="nav-item" role="presentation"><a class="nav-link gl-tab-nav-item" href="/url">Link</a></li>')
+ end
+
+ it 'creates a tab with block output' do
+ expect(gl_tab_link_to('/url') { 'block content' }).to match(/block content/)
+ end
+
+ it 'creates a tab with custom classes' do
+ expect(gl_tab_link_to('Link', '/url', { class: 'my-class' })).to match(/<a class=".*my-class.*"/)
+ end
+
+ it 'creates an active tab with item_active = true' do
+ expect(gl_tab_link_to('Link', '/url', { item_active: true })).to match(/<a class=".*active gl-tab-nav-item-active gl-tab-nav-item-active-indigo.*"/)
+ end
+
+ context 'when on the active page' do
+ before do
+ allow(self).to receive(:current_page?).and_return(true)
+ end
+
+ it 'creates an active tab' do
+ expect(gl_tab_link_to('Link', '/url')).to match(/<a class=".*active gl-tab-nav-item-active gl-tab-nav-item-active-indigo.*"/)
+ end
+
+ it 'creates an inactive tab with item_active = false' do
+ expect(gl_tab_link_to('Link', '/url', { item_active: false })).not_to match(/<a class=".*active.*"/)
+ end
+ end
+ end
+
describe 'nav_link' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/api/base_spec.rb b/spec/lib/api/base_spec.rb
new file mode 100644
index 00000000000..a73c4fcfdd8
--- /dev/null
+++ b/spec/lib/api/base_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# rubocop:disable Rails/HttpPositionalArguments
+RSpec.describe ::API::Base do
+ let(:app_hello) do
+ route = double(:route, request_method: 'GET', path: '/:version/test/hello')
+ double(:endpoint, route: route, options: { for: api_handler, path: ["hello"] }, namespace: '/test')
+ end
+
+ let(:app_hi) do
+ route = double(:route, request_method: 'GET', path: '/:version//test/hi')
+ double(:endpoint, route: route, options: { for: api_handler, path: ["hi"] }, namespace: '/test')
+ end
+
+ describe 'declare feature categories at handler level for all routes' do
+ let(:api_handler) do
+ Class.new(described_class) do
+ feature_category :foo
+ target_duration :fast
+
+ namespace '/test' do
+ get 'hello' do
+ end
+ post 'hi' do
+ end
+ end
+ end
+ end
+
+ it 'sets feature category for a particular route', :aggregate_failures do
+ expect(api_handler.feature_category_for_app(app_hello)).to eq(:foo)
+ expect(api_handler.feature_category_for_app(app_hi)).to eq(:foo)
+ end
+
+ it 'sets target duration for a particular route', :aggregate_failures do
+ expect(api_handler.target_duration_for_app(app_hello)).to be_a_target_duration(:fast)
+ expect(api_handler.target_duration_for_app(app_hi)).to be_a_target_duration(:fast)
+ end
+ end
+
+ describe 'declare feature categories at route level' do
+ let(:api_handler) do
+ Class.new(described_class) do
+ namespace '/test' do
+ get 'hello', feature_category: :foo, target_duration: :slow do
+ end
+ post 'hi', feature_category: :bar, target_duration: :fast do
+ end
+ end
+ end
+ end
+
+ it 'sets feature category for a particular route', :aggregate_failures do
+ expect(api_handler.feature_category_for_app(app_hello)).to eq(:foo)
+ expect(api_handler.feature_category_for_app(app_hi)).to eq(:bar)
+ end
+
+ it 'sets target duration for a particular route', :aggregate_failures do
+ expect(api_handler.target_duration_for_app(app_hello)).to be_a_target_duration(:slow)
+ expect(api_handler.target_duration_for_app(app_hi)).to be_a_target_duration(:fast)
+ end
+ end
+
+ describe 'declare feature categories at both handler level and route level' do
+ let(:api_handler) do
+ Class.new(described_class) do
+ feature_category :foo, ['/test/hello']
+ target_duration :slow, ['/test/hello']
+
+ namespace '/test' do
+ get 'hello' do
+ end
+ post 'hi', feature_category: :bar, target_duration: :fast do
+ end
+ end
+ end
+ end
+
+ it 'sets feature category for a particular route', :aggregate_failures do
+ expect(api_handler.feature_category_for_app(app_hello)).to eq(:foo)
+ expect(api_handler.feature_category_for_app(app_hi)).to eq(:bar)
+ end
+
+ it 'sets target duration for a particular route', :aggregate_failures do
+ expect(api_handler.target_duration_for_app(app_hello)).to be_a_target_duration(:slow)
+ expect(api_handler.target_duration_for_app(app_hi)).to be_a_target_duration(:fast)
+ end
+ end
+end
+# rubocop:enable Rails/HttpPositionalArguments
diff --git a/spec/lib/gitlab/endpoint_attributes_spec.rb b/spec/lib/gitlab/endpoint_attributes_spec.rb
new file mode 100644
index 00000000000..44df32d8f18
--- /dev/null
+++ b/spec/lib/gitlab/endpoint_attributes_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative "../../../lib/gitlab/endpoint_attributes"
+
+RSpec.describe Gitlab::EndpointAttributes do
+ let(:base_controller) do
+ Class.new do
+ include ::Gitlab::EndpointAttributes
+ end
+ end
+
+ let(:controller) do
+ Class.new(base_controller) do
+ feature_category :foo, %w(update edit)
+ feature_category :bar, %w(index show)
+ feature_category :quux, %w(destroy)
+
+ target_duration :fast, %w(do_a)
+ target_duration :slow, %w(do_b do_c)
+ end
+ end
+
+ let(:subclass) do
+ Class.new(controller) do
+ feature_category :baz, %w(subclass_index)
+ target_duration :very_fast, %w(superclass_do_something)
+ end
+ end
+
+ it "is nil when nothing was defined" do
+ expect(base_controller.feature_category_for_action("hello")).to be_nil
+ end
+
+ it "returns the expected category", :aggregate_failures do
+ expect(controller.feature_category_for_action("update")).to eq(:foo)
+ expect(controller.feature_category_for_action("index")).to eq(:bar)
+ expect(controller.feature_category_for_action("destroy")).to eq(:quux)
+ end
+
+ it "falls back to medium when target_duration was not defined", :aggregate_failures do
+ expect(base_controller.target_duration_for_action("hello")).to be_a_target_duration(:medium)
+ expect(controller.target_duration_for_action("update")).to be_a_target_duration(:medium)
+ expect(controller.target_duration_for_action("index")).to be_a_target_duration(:medium)
+ expect(controller.target_duration_for_action("destroy")).to be_a_target_duration(:medium)
+ end
+
+ it "returns the expected target_duration", :aggregate_failures do
+ expect(controller.target_duration_for_action("do_a")).to be_a_target_duration(:fast)
+ expect(controller.target_duration_for_action("do_b")).to be_a_target_duration(:slow)
+ expect(controller.target_duration_for_action("do_c")).to be_a_target_duration(:slow)
+ end
+
+ it "returns feature category for an implied action if not specify actions" do
+ klass = Class.new(base_controller) do
+ feature_category :foo
+ end
+ expect(klass.feature_category_for_action("index")).to eq(:foo)
+ expect(klass.feature_category_for_action("show")).to eq(:foo)
+ end
+
+ it "returns expected duration for an implied action if not specify actions" do
+ klass = Class.new(base_controller) do
+ feature_category :foo
+ target_duration :slow
+ end
+ expect(klass.target_duration_for_action("index")).to be_a_target_duration(:slow)
+ expect(klass.target_duration_for_action("show")).to be_a_target_duration(:slow)
+ end
+
+ it "returns the expected category for categories defined in subclasses" do
+ expect(subclass.feature_category_for_action("subclass_index")).to eq(:baz)
+ end
+
+ it "falls back to superclass's feature category" do
+ expect(subclass.feature_category_for_action("update")).to eq(:foo)
+ end
+
+ it "returns the expected target_duration for categories defined in subclasses" do
+ expect(subclass.target_duration_for_action("superclass_do_something")).to be_a_target_duration(:very_fast)
+ end
+
+ it "falls back to superclass's expected duration" do
+ expect(subclass.target_duration_for_action("do_a")).to be_a_target_duration(:fast)
+ end
+
+ it "raises an error when defining for the controller and for individual actions" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello
+ feature_category :goodbye, [:world]
+ end
+ end.to raise_error(ArgumentError, "feature_category are already defined for all actions, but re-defined for world")
+ end
+
+ it "raises an error when multiple calls define the same action" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, [:world]
+ feature_category :goodbye, ["world"]
+ end
+ end.to raise_error(ArgumentError, "Attributes re-defined for action world: feature_category")
+ end
+
+ it "raises an error when multiple calls define the same action" do
+ expect do
+ Class.new(base_controller) do
+ target_duration :fast, [:world]
+ target_duration :slow, ["world"]
+ end
+ end.to raise_error(ArgumentError, "Attributes re-defined for action world: target_duration")
+ end
+
+ it "does not raise an error when multiple calls define the same action and configs" do
+ expect do
+ Class.new(base_controller) do
+ feature_category :hello, [:world]
+ feature_category :hello, ["world"]
+ target_duration :fast, [:moon]
+ target_duration :fast, ["moon"]
+ end
+ end.not_to raise_error
+ end
+
+ it "raises an error if the expected duration is not supported" do
+ expect do
+ Class.new(base_controller) do
+ target_duration :super_slow
+ end
+ end.to raise_error(ArgumentError, "Target duration not supported: super_slow")
+ end
+end
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index e0962614763..493684b979c 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect(described_class).not_to receive(:http_health_requests_total)
expect(described_class)
.to receive_message_chain(:http_request_duration_seconds, :observe)
- .with({ method: 'get' }, a_positive_execution_time)
+ .with({ method: 'get' }, a_positive_execution_time)
subject.call(env)
end
@@ -161,6 +161,165 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
subject.call(env)
end
end
+
+ context 'SLI satisfactory' do
+ where(:target, :duration, :success) do
+ [
+ [:very_fast, 0.1, true],
+ [:very_fast, 0.25, false],
+ [:very_fast, 0.3, false],
+ [:fast, 0.3, true],
+ [:fast, 0.5, false],
+ [:fast, 0.6, false],
+ [:medium, 0.6, true],
+ [:medium, 1.0, false],
+ [:medium, 1.2, false],
+ [:slow, 4.5, true],
+ [:slow, 5.0, false],
+ [:slow, 6, false]
+ ]
+ end
+
+ with_them do
+ context 'Grape API handler having expected duration setup' do
+ let(:api_handler) do
+ target_duration = target # target is a DSL provided by Rspec, it's invisible to the inner block
+ Class.new(::API::Base) do
+ feature_category :hello_world, ['/projects/:id/archive']
+ target_duration target_duration, ['/projects/:id/archive']
+ end
+ end
+
+ let(:endpoint) do
+ route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
+ double(:endpoint, route: route,
+ options: { for: api_handler, path: [":id/archive"] },
+ namespace: "/projects")
+ end
+
+ let(:env) { { 'api.endpoint' => endpoint, 'REQUEST_METHOD' => 'GET' } }
+
+ before do
+ ::Gitlab::ApplicationContext.push(feature_category: 'hello_world', caller_id: 'GET /projects/:id/archive')
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100 + duration)
+ end
+
+ 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' },
+ success: success
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'Rails controller having expected duration setup' do
+ let(:controller) do
+ target_duration = target # target is a DSL provided by Rspec, it's invisible to the inner block
+ Class.new(ApplicationController) do
+ feature_category :hello_world, [:index, :show]
+ target_duration target_duration, [:index, :show]
+ end
+ end
+
+ let(:env) do
+ controller_instance = controller.new
+ controller_instance.action_name = :index
+ { 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
+ end
+
+ before do
+ ::Gitlab::ApplicationContext.push(feature_category: 'hello_world', caller_id: 'AnonymousController#index')
+ allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 100 + duration)
+ end
+
+ it "captures SLI metrics" do
+ expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment).with(
+ labels: { feature_category: 'hello_world', endpoint_id: 'AnonymousController#index' },
+ success: success
+ )
+ subject.call(env)
+ end
+ end
+ end
+
+ context 'Grape API without expected duration' do
+ let(:endpoint) do
+ route = double(:route, request_method: 'GET', path: '/:version/projects/:id/archive(.:format)')
+ double(:endpoint, route: route,
+ options: { for: api_handler, path: [":id/archive"] },
+ namespace: "/projects")
+ end
+
+ let(:env) { { 'api.endpoint' => endpoint, 'REQUEST_METHOD' => 'GET' } }
+
+ let(:api_handler) { Class.new(::API::Base) }
+
+ it "falls back request's expectation to medium (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' },
+ 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' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'Rails controller without expected duration' do
+ let(:controller) { Class.new(ApplicationController) }
+
+ let(:env) do
+ controller_instance = controller.new
+ controller_instance.action_name = :index
+ { 'action_controller.instance' => controller_instance, 'REQUEST_METHOD' => 'GET' }
+ end
+
+ it "falls back request's expectation to medium (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' },
+ 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' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+
+ context 'An unknown request' do
+ let(:env) do
+ { 'REQUEST_METHOD' => 'GET' }
+ end
+
+ it "falls back request's expectation to medium (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' },
+ 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' },
+ success: false
+ )
+ subject.call(env)
+ end
+ end
+ end
end
describe '.initialize_metrics', :prometheus do
diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb
index 3be7ec17e45..ecdcc23e588 100644
--- a/spec/lib/gitlab/rack_attack/request_spec.rb
+++ b/spec/lib/gitlab/rack_attack/request_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::RackAttack::Request do
+ using RSpec::Parameterized::TableSyntax
+
describe 'FILES_PATH_REGEX' do
subject { described_class::FILES_PATH_REGEX }
@@ -13,4 +15,33 @@ RSpec.describe Gitlab::RackAttack::Request do
it { is_expected.to match('/api/v4/projects/some%2Fnested%2Frepo/repository/files/README') }
it { is_expected.not_to match('/api/v4/projects/some/nested/repo/repository/files/README') }
end
+
+ describe '#deprecated_api_request?' do
+ let(:env) { { 'REQUEST_METHOD' => 'GET', 'rack.input' => StringIO.new, 'PATH_INFO' => path, 'QUERY_STRING' => query } }
+ let(:request) { ::Rack::Attack::Request.new(env) }
+
+ subject { !!request.__send__(:deprecated_api_request?) }
+
+ where(:path, :query, :expected) do
+ '/' | '' | false
+
+ '/api/v4/groups/1/' | '' | true
+ '/api/v4/groups/1' | '' | true
+ '/api/v4/groups/foo/' | '' | true
+ '/api/v4/groups/foo' | '' | true
+
+ '/api/v4/groups/1' | 'with_projects=' | true
+ '/api/v4/groups/1' | 'with_projects=1' | true
+ '/api/v4/groups/1' | 'with_projects=0' | false
+
+ '/foo/api/v4/groups/1' | '' | false
+ '/api/v4/groups/1/foo' | '' | false
+
+ '/api/v4/groups/nested%2Fgroup' | '' | true
+ end
+
+ with_them do
+ it { is_expected.to eq(expected) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/request_endpoints_spec.rb b/spec/lib/gitlab/request_endpoints_spec.rb
index 226605f6e2e..0c939bfb0ee 100644
--- a/spec/lib/gitlab/request_endpoints_spec.rb
+++ b/spec/lib/gitlab/request_endpoints_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::RequestEndpoints do
it 'selects all feature API classes' do
api_classes = described_class.all_api_endpoints.map { |route| route.app.options[:for] }
- expect(api_classes).to all(include(Gitlab::WithFeatureCategory))
+ expect(api_classes).to all(include(Gitlab::EndpointAttributes))
end
end
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::RequestEndpoints do
controller_classes = all_controller_actions.map(&:first)
all_actions = all_controller_actions.map(&:last)
- expect(controller_classes).to all(include(Gitlab::WithFeatureCategory))
+ expect(controller_classes).to all(include(Gitlab::EndpointAttributes))
expect(controller_classes).not_to include(ApplicationController, Devise::UnlocksController)
expect(all_actions).to all(be_a(String))
end
diff --git a/spec/lib/gitlab/with_feature_category_spec.rb b/spec/lib/gitlab/with_feature_category_spec.rb
deleted file mode 100644
index b6fe1c84b26..00000000000
--- a/spec/lib/gitlab/with_feature_category_spec.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require_relative "../../../lib/gitlab/with_feature_category"
-
-RSpec.describe Gitlab::WithFeatureCategory do
- describe ".feature_category_for_action" do
- let(:base_controller) do
- Class.new do
- include ::Gitlab::WithFeatureCategory
- end
- end
-
- let(:controller) do
- Class.new(base_controller) do
- feature_category :foo, %w(update edit)
- feature_category :bar, %w(index show)
- feature_category :quux, %w(destroy)
- end
- end
-
- let(:subclass) do
- Class.new(controller) do
- feature_category :baz, %w(subclass_index)
- end
- end
-
- it "is nil when nothing was defined" do
- expect(base_controller.feature_category_for_action("hello")).to be_nil
- end
-
- it "returns the expected category", :aggregate_failures do
- expect(controller.feature_category_for_action("update")).to eq(:foo)
- expect(controller.feature_category_for_action("index")).to eq(:bar)
- expect(controller.feature_category_for_action("destroy")).to eq(:quux)
- end
-
- it "returns the expected category for categories defined in subclasses" do
- expect(subclass.feature_category_for_action("subclass_index")).to eq(:baz)
- end
-
- it "raises an error when defining for the controller and for individual actions" do
- expect do
- Class.new(base_controller) do
- feature_category :hello
- feature_category :goodbye, [:world]
- end
- end.to raise_error(ArgumentError, "hello is defined for all actions, but other categories are set")
- end
-
- it "raises an error when multiple calls define the same action" do
- expect do
- Class.new(base_controller) do
- feature_category :hello, [:world]
- feature_category :goodbye, ["world"]
- end
- end.to raise_error(ArgumentError, "Actions have multiple feature categories: world")
- end
-
- it "does not raise an error when multiple calls define the same action and feature category" do
- expect do
- Class.new(base_controller) do
- feature_category :hello, [:world]
- feature_category :hello, ["world"]
- end
- end.not_to raise_error
- end
- end
-end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 3e264867703..26c116e094b 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -946,6 +946,10 @@ RSpec.describe ApplicationSetting do
throttle_unauthenticated_files_api_period_in_seconds
throttle_authenticated_files_api_requests_per_period
throttle_authenticated_files_api_period_in_seconds
+ throttle_unauthenticated_deprecated_api_requests_per_period
+ throttle_unauthenticated_deprecated_api_period_in_seconds
+ throttle_authenticated_deprecated_api_requests_per_period
+ throttle_authenticated_deprecated_api_period_in_seconds
throttle_authenticated_git_lfs_requests_per_period
throttle_authenticated_git_lfs_period_in_seconds
]
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index 2d52cddcacc..ace8c59e82d 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -92,9 +92,9 @@ RSpec.describe 'get board lists' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
- let(:first_param) { 2 }
- let(:expected_results) { lists.map { |list| global_id_of(list) } }
+ let(:sort_param) { }
+ let(:first_param) { 2 }
+ let(:all_records) { lists.map { |list| global_id_of(list) } }
end
end
end
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 778fe5b129e..51a07e60e15 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -95,9 +95,9 @@ RSpec.describe 'Query.runners' do
let(:ordered_runners) { runners.sort_by(&:contacted_at) }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CONTACTED_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { ordered_runners.map(&:id) }
+ let(:sort_param) { :CONTACTED_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { ordered_runners.map(&:id) }
end
end
@@ -105,9 +105,9 @@ RSpec.describe 'Query.runners' do
let(:ordered_runners) { runners.sort_by(&:created_at).reverse }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { ordered_runners.map(&:id) }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { ordered_runners.map(&:id) }
end
end
end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 414847c9c93..d5410f1a7cb 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -106,10 +106,10 @@ RSpec.describe 'getting projects' do
context 'when sorting by similarity' do
it_behaves_like 'sorted paginated query' do
- let(:node_path) { %w[name] }
- let(:sort_param) { :SIMILARITY }
- let(:first_param) { 2 }
- let(:expected_results) { [project_3.name, project_2.name, project_4.name] }
+ let(:node_path) { %w[name] }
+ let(:sort_param) { :SIMILARITY }
+ let(:first_param) { 2 }
+ let(:all_records) { [project_3.name, project_2.name, project_4.name] }
end
end
end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index 3ad56223b61..9061e5ff40c 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -190,7 +190,7 @@ RSpec.describe 'getting container repositories in a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :NAME_ASC }
let(:first_param) { 2 }
- let(:expected_results) { [container_repository2.name, container_repository1.name, container_repository4.name, container_repository3.name, container_repository5.name] }
+ let(:all_records) { [container_repository2.name, container_repository1.name, container_repository4.name, container_repository3.name, container_repository5.name] }
end
end
@@ -198,7 +198,7 @@ RSpec.describe 'getting container repositories in a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :NAME_DESC }
let(:first_param) { 2 }
- let(:expected_results) { [container_repository5.name, container_repository3.name, container_repository4.name, container_repository1.name, container_repository2.name] }
+ let(:all_records) { [container_repository5.name, container_repository3.name, container_repository4.name, container_repository1.name, container_repository2.name] }
end
end
end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index c6b4d82bf15..82c207a0918 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -205,7 +205,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :DUE_DATE_ASC }
let(:first_param) { 2 }
- let(:expected_results) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
+ let(:all_records) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
end
end
@@ -213,7 +213,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :DUE_DATE_DESC }
let(:first_param) { 2 }
- let(:expected_results) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
+ let(:all_records) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
end
end
end
@@ -230,7 +230,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :RELATIVE_POSITION_ASC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[
relative_issue5.iid, relative_issue3.iid, relative_issue1.iid,
relative_issue4.iid, relative_issue2.iid
@@ -256,7 +256,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :PRIORITY_ASC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[
priority_issue3.iid, priority_issue1.iid,
priority_issue2.iid, priority_issue4.iid
@@ -269,7 +269,7 @@ RSpec.describe 'getting an issue list for a project' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { :PRIORITY_DESC }
let(:first_param) { 2 }
- let(:expected_results) do
+ let(:all_records) do
[priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid]
end
end
@@ -288,17 +288,17 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :LABEL_PRIORITY_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
+ let(:sort_param) { :LABEL_PRIORITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :LABEL_PRIORITY_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
+ let(:sort_param) { :LABEL_PRIORITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
end
end
end
@@ -313,17 +313,17 @@ RSpec.describe 'getting an issue list for a project' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :MILESTONE_DUE_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
+ let(:sort_param) { :MILESTONE_DUE_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :MILESTONE_DUE_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
+ let(:sort_param) { :MILESTONE_DUE_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
end
end
end
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 1b0405be09c..b0bedd99fce 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -385,7 +385,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
context 'when sorting by merged_at DESC' do
let(:sort_param) { :MERGED_AT_DESC }
- let(:expected_results) do
+ let(:all_records) do
[
merge_request_b,
merge_request_d,
@@ -418,14 +418,14 @@ RSpec.describe 'getting merge request listings nested in a project' do
query = pagination_query(params)
post_graphql(query, current_user: current_user)
- expect(results.map { |item| item["id"] }).to eq(expected_results.last(2))
+ expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
end
end
end
context 'when sorting by closed_at DESC' do
let(:sort_param) { :CLOSED_AT_DESC }
- let(:expected_results) do
+ let(:all_records) do
[
merge_request_b,
merge_request_d,
@@ -458,7 +458,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
query = pagination_query(params)
post_graphql(query, current_user: current_user)
- expect(results.map { |item| item["id"] }).to eq(expected_results.last(2))
+ expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
end
end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
index 8ccdb955ed9..2816ce90a6b 100644
--- a/spec/requests/api/graphql/project/releases_spec.rb
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -322,17 +322,17 @@ RSpec.describe 'Query.project(fullPath).releases()' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :RELEASED_AT_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
+ let(:sort_param) { :RELEASED_AT_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :RELEASED_AT_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
+ let(:sort_param) { :RELEASED_AT_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
@@ -346,17 +346,17 @@ RSpec.describe 'Query.project(fullPath).releases()' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_ASC }
- let(:first_param) { 2 }
- let(:expected_results) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
+ let(:sort_param) { :CREATED_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release1.tag, release2.tag, release3.tag, release4.tag, release5.tag] }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 2 }
- let(:expected_results) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [release5.tag, release4.tag, release3.tag, release2.tag, release1.tag] }
end
end
end
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index 22b68fbc9bb..67cd35ee545 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -114,17 +114,17 @@ RSpec.describe 'Users' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_ASC }
- let(:first_param) { 1 }
- let(:expected_results) { ascending_users }
+ let(:sort_param) { :CREATED_ASC }
+ let(:first_param) { 1 }
+ let(:all_records) { ascending_users }
end
end
context 'when descending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { :CREATED_DESC }
- let(:first_param) { 1 }
- let(:expected_results) { ascending_users.reverse }
+ let(:sort_param) { :CREATED_DESC }
+ let(:first_param) { 1 }
+ let(:all_records) { ascending_users.reverse }
end
end
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index 87ef6fa1a18..904bfd3e7c3 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -30,7 +30,11 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
throttle_unauthenticated_files_api_requests_per_period: 100,
throttle_unauthenticated_files_api_period_in_seconds: 1,
throttle_authenticated_files_api_requests_per_period: 100,
- throttle_authenticated_files_api_period_in_seconds: 1
+ throttle_authenticated_files_api_period_in_seconds: 1,
+ throttle_unauthenticated_deprecated_api_requests_per_period: 100,
+ throttle_unauthenticated_deprecated_api_period_in_seconds: 1,
+ throttle_authenticated_deprecated_api_requests_per_period: 100,
+ throttle_authenticated_deprecated_api_period_in_seconds: 1
}
end
@@ -790,6 +794,213 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
+ describe 'Deprecated API', :api do
+ let_it_be(:group) { create(:group, :public) }
+
+ let(:request_method) { 'GET' }
+ let(:path) { "/groups/#{group.id}" }
+ let(:params) { {} }
+
+ context 'unauthenticated' do
+ let(:throttle_setting_prefix) { 'throttle_unauthenticated_deprecated_api' }
+
+ def do_request
+ get(api(path), params: params)
+ end
+
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_deprecated_api_period_in_seconds] = period_in_seconds
+ end
+
+ context 'when unauthenticated deprecated api throttle is disabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_enabled] = false
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'allows requests over the rate limit' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when unauthenticated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the unauthenticated api rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+
+ context 'when unauthenticated web throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_web_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_unauthenticated_web_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_web_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores unauthenticated web throttle' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+ end
+
+ context 'when unauthenticated deprecated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_unauthenticated_deprecated_api_requests_per_period] = requests_per_period # 1
+ settings_to_set[:throttle_unauthenticated_deprecated_api_period_in_seconds] = period_in_seconds # 10_000
+ settings_to_set[:throttle_unauthenticated_deprecated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ context 'when group endpoint is given with_project=false' do
+ let(:params) { { with_projects: false } }
+
+ it 'permits requests over the rate limit' do
+ (1 + requests_per_period).times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ it 'rejects requests over the rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+
+ context 'when unauthenticated api throttle is lower' do
+ before do
+ settings_to_set[:throttle_unauthenticated_api_requests_per_period] = 0
+ settings_to_set[:throttle_unauthenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_unauthenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores unauthenticated api throttle' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+
+ it_behaves_like 'tracking when dry-run mode is set' do
+ let(:throttle_name) { 'throttle_unauthenticated_deprecated_api' }
+ end
+ end
+ end
+
+ context 'authenticated' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:member) { group.add_owner(user) }
+ let_it_be(:token) { create(:personal_access_token, user: user) }
+ let_it_be(:other_user) { create(:user) }
+ let_it_be(:other_user_token) { create(:personal_access_token, user: other_user) }
+
+ let(:throttle_setting_prefix) { 'throttle_authenticated_deprecated_api' }
+
+ before do
+ stub_application_setting(settings_to_set)
+ end
+
+ context 'with the token in the query string' do
+ let(:request_args) { [api(path, personal_access_token: token), {}] }
+ let(:other_user_request_args) { [api(path, personal_access_token: other_user_token), {}] }
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'with the token in the headers' do
+ let(:request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(token)) }
+ let(:other_user_request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(other_user_token)) }
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'precedence over authenticated api throttle' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_deprecated_api_period_in_seconds] = period_in_seconds
+ end
+
+ def do_request
+ get(api(path, personal_access_token: token), params: params)
+ end
+
+ context 'when authenticated deprecated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_enabled] = true
+ end
+
+ context 'when authenticated api throttle is lower' do
+ before do
+ settings_to_set[:throttle_authenticated_api_requests_per_period] = 0
+ settings_to_set[:throttle_authenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores authenticated api throttle' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+ end
+
+ context 'when authenticated deprecated api throttle is disabled' do
+ before do
+ settings_to_set[:throttle_authenticated_deprecated_api_enabled] = false
+ end
+
+ context 'when authenticated api throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_api_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_api_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_api_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the authenticated api rate limit' do
+ requests_per_period.times do
+ do_request
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ expect_rejection { do_request }
+ end
+ end
+ end
+ end
+ end
+ end
+
describe 'throttle bypass header' do
let(:headers) { {} }
let(:bypass_header) { 'gitlab-bypass-rate-limiting' }
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index a1fd89bcad7..5c9d2c5e680 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -413,6 +413,32 @@ RSpec.describe ApplicationSettings::UpdateService do
end
end
+ context 'when deprecated API rate limits are passed' do
+ let(:params) do
+ {
+ throttle_unauthenticated_deprecated_api_enabled: 1,
+ throttle_unauthenticated_deprecated_api_period_in_seconds: 500,
+ throttle_unauthenticated_deprecated_api_requests_per_period: 20,
+ throttle_authenticated_deprecated_api_enabled: 1,
+ throttle_authenticated_deprecated_api_period_in_seconds: 600,
+ throttle_authenticated_deprecated_api_requests_per_period: 10
+ }
+ end
+
+ it 'updates deprecated API throttle settings' do
+ subject.execute
+
+ application_settings.reload
+
+ expect(application_settings.throttle_unauthenticated_deprecated_api_enabled).to be_truthy
+ expect(application_settings.throttle_unauthenticated_deprecated_api_period_in_seconds).to eq(500)
+ expect(application_settings.throttle_unauthenticated_deprecated_api_requests_per_period).to eq(20)
+ expect(application_settings.throttle_authenticated_deprecated_api_enabled).to be_truthy
+ expect(application_settings.throttle_authenticated_deprecated_api_period_in_seconds).to eq(600)
+ expect(application_settings.throttle_authenticated_deprecated_api_requests_per_period).to eq(10)
+ end
+ end
+
context 'when git lfs rate limits are passed' do
let(:params) do
{
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 02997096021..d67b189f90e 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -1935,6 +1935,21 @@ RSpec.describe QuickActions::InterpretService do
it_behaves_like 'relate command'
end
+ context 'when quick action target is unpersisted' do
+ let(:issue) { build(:issue, project: project) }
+ let(:other_issue) { create(:issue, project: project) }
+ let(:issues_related) { [other_issue] }
+ let(:content) { "/relate #{other_issue.to_reference}" }
+
+ it 'relates the issues after the issue is persisted' do
+ service.execute(content, issue)
+
+ issue.save!
+
+ expect(IssueLink.where(source: issue).map(&:target)).to match_array(issues_related)
+ end
+ end
+
context 'empty relate command' do
let(:issues_related) { [] }
let(:content) { '/relate' }
diff --git a/spec/support/database/cross-join-allowlist.yml b/spec/support/database/cross-join-allowlist.yml
index 839b8436900..52318ea2232 100644
--- a/spec/support/database/cross-join-allowlist.yml
+++ b/spec/support/database/cross-join-allowlist.yml
@@ -9,7 +9,6 @@
- "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb"
- "./ee/spec/finders/security/findings_finder_spec.rb"
- "./ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb"
-- "./ee/spec/lib/analytics/devops_adoption/snapshot_calculator_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/migrate_security_scans_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb"
diff --git a/spec/support/matchers/be_a_target_duration.rb b/spec/support/matchers/be_a_target_duration.rb
new file mode 100644
index 00000000000..f4efc89d3e0
--- /dev/null
+++ b/spec/support/matchers/be_a_target_duration.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec::Matchers.define :be_a_target_duration do |expected|
+ match do |actual|
+ actual.is_a?(::Gitlab::EndpointAttributes::Config::Duration) && actual.name == expected
+ end
+end
diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
index 7adf303bde4..85434ba7afd 100644
--- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'it uploads and commits a new text file' do
+RSpec.shared_examples 'it uploads and commits a new text file' do |drop: false|
it 'uploads and commits a new text file', :js do
find('.add-to-tree').click
@@ -10,7 +10,11 @@ RSpec.shared_examples 'it uploads and commits a new text file' do
wait_for_requests
end
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ end
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -32,7 +36,7 @@ RSpec.shared_examples 'it uploads and commits a new text file' do
end
end
-RSpec.shared_examples 'it uploads and commits a new image file' do
+RSpec.shared_examples 'it uploads and commits a new image file' do |drop: false|
it 'uploads and commits a new image file', :js do
find('.add-to-tree').click
@@ -42,7 +46,11 @@ RSpec.shared_examples 'it uploads and commits a new image file' do
wait_for_requests
end
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true)
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true)
+ end
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -58,7 +66,7 @@ RSpec.shared_examples 'it uploads and commits a new image file' do
end
end
-RSpec.shared_examples 'it uploads and commits a new pdf file' do
+RSpec.shared_examples 'it uploads and commits a new pdf file' do |drop: false|
it 'uploads and commits a new pdf file', :js do
find('.add-to-tree').click
@@ -68,7 +76,11 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do
wait_for_requests
end
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true)
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true)
+ end
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -84,7 +96,7 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do
end
end
-RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
+RSpec.shared_examples 'it uploads and commits a new file to a forked project' do |drop: false|
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
@@ -100,7 +112,12 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
find('.add-to-tree').click
click_link('Upload file')
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ end
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -123,7 +140,7 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
end
end
-RSpec.shared_examples 'it uploads a file to a sub-directory' do
+RSpec.shared_examples 'it uploads a file to a sub-directory' do |drop: false|
it 'uploads a file to a sub-directory', :js do
click_link 'files'
@@ -133,7 +150,12 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do
find('.add-to-tree').click
click_link('Upload file')
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ end
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
@@ -150,11 +172,15 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do
end
end
-RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do
+RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do |drop: false|
it 'uploads and commits a new text file via "upload file" button', :js do
find('[data-testid="upload-file-button"]').click
- attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ if drop
+ find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+ else
+ attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
+ end
page.within('#details-modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index eaeb5faee3b..37a805902a9 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -9,7 +9,7 @@
# data_path: the keys necessary to dig into the return GraphQL data to get the
# returned results
# first_param: number of items expected (like a page size)
-# expected_results: array of comparison data of all items sorted correctly
+# all_records: array of comparison data of all items sorted correctly
# pagination_query: method that specifies the GraphQL query
# pagination_results_data: method that extracts the sorted data used to compare against
# the expected results
@@ -38,9 +38,9 @@
# let(:ordered_issues) { issues.sort_by(&:weight) }
#
# it_behaves_like 'sorted paginated query' do
-# let(:sort_param) { :WEIGHT_ASC }
-# let(:first_param) { 2 }
-# let(:expected_results) { ordered_issues.map(&:iid) }
+# let(:sort_param) { :WEIGHT_ASC }
+# let(:first_param) { 2 }
+# let(:all_records) { ordered_issues.map(&:iid) }
# end
# end
#
@@ -51,7 +51,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
let(:node_path) { ['id'] }
it_behaves_like 'requires variables' do
- let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] }
+ let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] }
end
describe do
@@ -101,13 +101,13 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
context 'when sorting' do
it 'sorts correctly' do
- expect(results).to eq expected_results
+ expect(results).to eq all_records
end
context 'when paginating' do
let(:params) { sort_argument.merge(first: first_param) }
- let(:first_page) { expected_results.first(first_param) }
- let(:rest) { expected_results.drop(first_param) }
+ let(:first_page) { all_records.first(first_param) }
+ let(:rest) { all_records.drop(first_param) }
it 'paginates correctly' do
expect(results).to eq first_page
@@ -130,7 +130,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}|
it 'fetches last elements without error' do
post_graphql(pagination_query(params), current_user: current_user)
- expect(results.first).to eq(expected_results.last)
+ expect(results.first).to eq(all_records.last)
end
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
index 274516cd87b..01ed6c26576 100644
--- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb
@@ -62,9 +62,10 @@ RSpec.shared_examples 'group and project boards query' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
- let(:first_param) { 2 }
- let(:expected_results) do
+ let(:sort_param) { }
+ let(:first_param) { 2 }
+
+ let(:all_records) do
if board_parent.multiple_issue_boards_available?
boards.map { |board| global_id_of(board) }
else
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index f1d8665468c..367c6d4fa3a 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -101,7 +101,7 @@ RSpec.shared_examples 'group and project packages query' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
- let(:expected_results) { ascending_packages }
+ let(:all_records) { ascending_packages }
end
end
end
@@ -111,7 +111,7 @@ RSpec.shared_examples 'group and project packages query' do
it_behaves_like 'sorted paginated query' do
let(:sort_param) { order }
let(:first_param) { 4 }
- let(:expected_results) { ascending_packages.reverse }
+ let(:all_records) { ascending_packages.reverse }
end
end
end
diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
index 2a19ff6f590..b294467d482 100644
--- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
+++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#
# Requires let variables:
-# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api"
+# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api", "throttle_authenticated_deprecated_api"
# * request_method
# * request_args
# * other_user_request_args
@@ -16,7 +16,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
"throttle_authenticated_web" => "throttle_authenticated_web",
"throttle_authenticated_packages_api" => "throttle_authenticated_packages_api",
"throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs",
- "throttle_authenticated_files_api" => "throttle_authenticated_files_api"
+ "throttle_authenticated_files_api" => "throttle_authenticated_files_api",
+ "throttle_authenticated_deprecated_api" => "throttle_authenticated_deprecated_api"
}
end