summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/issue_templates/InfraDev.md56
-rw-r--r--.rubocop_manual_todo.yml22
-rw-r--r--app/assets/javascripts/content_editor/extensions/description_item.js51
-rw-r--r--app/assets/javascripts/content_editor/extensions/description_list.js23
-rw-r--r--app/assets/javascripts/content_editor/services/create_content_editor.js4
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js8
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js8
-rw-r--r--app/assets/javascripts/diffs/components/app.vue59
-rw-r--r--app/assets/javascripts/vue_shared/components/code_block.vue7
-rw-r--r--app/assets/stylesheets/components/content_editor.scss18
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/pages/login.scss5
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss6
-rw-r--r--app/helpers/application_settings_helper.rb3
-rw-r--r--app/models/application_setting.rb8
-rw-r--r--app/models/application_setting_implementation.rb3
-rw-r--r--app/views/admin/application_settings/_git_lfs_limits.html.haml21
-rw-r--r--app/views/admin/application_settings/network.html.haml12
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml4
-rw-r--r--db/migrate/20210830085837_add_throttle_authenticated_git_lfs_columns.rb23
-rw-r--r--db/schema_migrations/202108300858371
-rw-r--r--db/structure.sql3
-rw-r--r--doc/administration/instance_limits.md10
-rw-r--r--doc/api/graphql/reference/index.md140
-rw-r--r--doc/api/members.md2
-rw-r--r--doc/architecture/blueprints/container_registry_metadata_database/index.md44
-rw-r--r--doc/ci/index.md10
-rw-r--r--doc/ci/migration/jenkins.md2
-rw-r--r--doc/development/application_limits.md4
-rw-r--r--doc/development/avoiding_downtime_in_migrations.md24
-rw-r--r--doc/development/background_migrations.md8
-rw-r--r--doc/development/cascading_settings.md2
-rw-r--r--doc/development/database/add_foreign_key_to_existing_column.md12
-rw-r--r--doc/development/database/not_null_constraints.md16
-rw-r--r--doc/development/database/rename_database_tables.md4
-rw-r--r--doc/development/database/strings_and_the_text_data_type.md21
-rw-r--r--doc/development/database/table_partitioning.md6
-rw-r--r--doc/development/i18n/externalization.md39
-rw-r--r--doc/development/migration_style_guide.md67
-rw-r--r--doc/development/prometheus_metrics.md4
-rw-r--r--doc/development/sidekiq_style_guide.md4
-rw-r--r--doc/development/single_table_inheritance.md4
-rw-r--r--doc/development/sql.md4
-rw-r--r--doc/development/testing_guide/best_practices.md76
-rw-r--r--doc/security/rate_limits.md1
-rw-r--r--doc/user/admin_area/settings/git_lfs_rate_limits.md35
-rw-r--r--doc/user/admin_area/settings/index.md1
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md1
-rw-r--r--generator_templates/active_record/migration/migration.rb5
-rw-r--r--generator_templates/post_deployment_migration/post_deployment_migration/migration.rb10
-rw-r--r--lib/gitlab/auth/auth_finders.rb4
-rw-r--r--lib/gitlab/database/migration.rb36
-rw-r--r--lib/gitlab/metrics/subscribers/rack_attack.rb3
-rw-r--r--lib/gitlab/path_regex.rb4
-rw-r--r--lib/gitlab/rack_attack.rb6
-rw-r--r--lib/gitlab/rack_attack/request.rb10
-rw-r--r--lib/gitlab/throttle.rb7
-rw-r--r--locale/gitlab.pot21
-rw-r--r--rubocop/cop/migration/versioned_migration_class.rb55
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js43
-rw-r--r--spec/frontend/deploy_freeze/helpers.js2
-rw-r--r--spec/frontend/diffs/create_diffs_store.js6
-rw-r--r--spec/frontend/fixtures/api_markdown.yml18
-rw-r--r--spec/frontend/fixtures/freeze_period.rb9
-rw-r--r--spec/frontend/fixtures/timezones.rb22
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap4
-rw-r--r--spec/lib/gitlab/database/migration_spec.rb34
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb10
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb93
-rw-r--r--spec/rubocop/cop/migration/versioned_migration_class_spec.rb69
-rw-r--r--spec/services/application_settings/update_service_spec.rb20
-rw-r--r--spec/support/shared_examples/requests/rack_attack_shared_examples.rb10
73 files changed, 1063 insertions, 328 deletions
diff --git a/.gitlab/issue_templates/InfraDev.md b/.gitlab/issue_templates/InfraDev.md
new file mode 100644
index 00000000000..bc0e65c3c22
--- /dev/null
+++ b/.gitlab/issue_templates/InfraDev.md
@@ -0,0 +1,56 @@
+<!--
+Triage of infradev Issues is desired to occur asynchronously.
+For maximum efficiency, please ensure the following, so that your infradev issues can gain maximum traction.
+
+https://about.gitlab.com/handbook/engineering/workflow/#a-guide-to-creating-effective-infradev-issues
+-->
+
+## Summary
+<!--
+Clearly state the scope of the problem, and how it affects GitLab.com
+-->
+
+
+## Impact
+<!--
+- Quantify the effect of the problem to help ensure that correct prioritization occurs.
+- Include costs to availability. The Incident Budget Explorer dashboard can help here.
+- Include the number of times alerts have fired owing to the problem, how much time was spent dealing with the problem, and how many people were involved.
+- Link to affected incidents, and cross-reference them as related issues.
+- Include screenshots of visualization from Grafana or Kibana.
+- Always include a permalink to the source of the screenshot so that others can investigate further.
+-->
+
+
+## Recommendation
+<!--
+Provide a clear, unambiguous, self-contained solution to the problem.
+-->
+
+
+## Verification
+<!--
+Provide a method for validating that the original issue still exists.
+
+Having a way of checking validity can save on a great deal of back-and-forth discussion between Infradev Triage participants including Engineering Managers, Directors and Product Managers and make space for other non-resolved issues to get scheduled sooner.
+
+Ideally, provide a link to a Thanos query or an ELK query and clear instructions on how to interpret the results to determine whether the problem is still occurring.
+-->
+
+
+<!--
+Workflow and other relevant labels
+
+/label ~"severity::"
+/label ~"priority::"
+/label ~"group::"
+/label ~"devops::"
+
+See also:
+- https://about.gitlab.com/handbook/engineering/quality/issue-triage/#availability
+- https://about.gitlab.com/handbook/product/categories/
+- https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml
+-->
+
+/label ~"infradev"
+/label ~"bug"
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index 42b425bf7d7..39ff015e296 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -23,28 +23,6 @@ Graphql/Descriptions:
- 'ee/app/graphql/types/vulnerability_severity_enum.rb'
- 'ee/app/graphql/types/vulnerability_state_enum.rb'
- 'ee/app/graphql/types/vulnerability_confidence_enum.rb'
- - 'ee/app/graphql/mutations/app_sec/fuzzing/api/ci_configuration/create.rb'
- - 'ee/app/graphql/mutations/boards/epic_boards/create.rb'
- - 'ee/app/graphql/mutations/boards/epic_boards/epic_move_list.rb'
- - 'ee/app/graphql/mutations/boards/epic_boards/update.rb'
- - 'ee/app/graphql/mutations/boards/epic_lists/destroy.rb'
- - 'ee/app/graphql/mutations/boards/lists/update_limit_metrics.rb'
- - 'ee/app/graphql/mutations/boards/update_epic_user_preferences.rb'
- - 'ee/app/graphql/mutations/compliance_management/frameworks/create.rb'
- - 'ee/app/graphql/mutations/compliance_management/frameworks/destroy.rb'
- - 'ee/app/graphql/mutations/compliance_management/frameworks/update.rb'
- - 'ee/app/graphql/mutations/concerns/mutations/shared_epic_arguments.rb'
- - 'ee/app/graphql/mutations/dast/profiles/create.rb'
- - 'ee/app/graphql/mutations/dast/profiles/update.rb'
- - 'ee/app/graphql/mutations/dast_on_demand_scans/create.rb'
- - 'ee/app/graphql/mutations/dast_scanner_profiles/create.rb'
- - 'ee/app/graphql/mutations/dast_scanner_profiles/update.rb'
- - 'ee/app/graphql/mutations/dast_site_profiles/create.rb'
- - 'ee/app/graphql/mutations/dast_site_profiles/delete.rb'
- - 'ee/app/graphql/mutations/dast_site_profiles/update.rb'
- - 'ee/app/graphql/mutations/dast_site_tokens/create.rb'
- - 'ee/app/graphql/mutations/dast_site_validations/create.rb'
- - 'ee/app/graphql/mutations/dast_site_validations/revoke.rb'
- 'ee/app/graphql/mutations/epic_tree/reorder.rb'
- 'ee/app/graphql/mutations/epics/add_issue.rb'
- 'ee/app/graphql/mutations/epics/base.rb'
diff --git a/app/assets/javascripts/content_editor/extensions/description_item.js b/app/assets/javascripts/content_editor/extensions/description_item.js
new file mode 100644
index 00000000000..018a6d8769b
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/description_item.js
@@ -0,0 +1,51 @@
+import { Node, mergeAttributes } from '@tiptap/core';
+
+export default Node.create({
+ name: 'descriptionItem',
+ content: 'block+',
+ defining: true,
+
+ addAttributes() {
+ return {
+ isTerm: {
+ default: true,
+ parseHTML: (element) => ({
+ isTerm: element.tagName.toLowerCase() === 'dt',
+ }),
+ },
+ };
+ },
+
+ parseHTML() {
+ return [{ tag: 'dt' }, { tag: 'dd' }];
+ },
+
+ renderHTML({ HTMLAttributes: { isTerm, ...HTMLAttributes } }) {
+ return [
+ 'li',
+ mergeAttributes(HTMLAttributes, { class: isTerm ? 'dl-term' : 'dl-description' }),
+ 0,
+ ];
+ },
+
+ addKeyboardShortcuts() {
+ return {
+ Enter: () => {
+ return this.editor.commands.splitListItem('descriptionItem');
+ },
+ Tab: () => {
+ const { isTerm } = this.editor.getAttributes('descriptionItem');
+ if (isTerm)
+ return this.editor.commands.updateAttributes('descriptionItem', { isTerm: !isTerm });
+
+ return false;
+ },
+ 'Shift-Tab': () => {
+ const { isTerm } = this.editor.getAttributes('descriptionItem');
+ if (isTerm) return this.editor.commands.liftListItem('descriptionItem');
+
+ return this.editor.commands.updateAttributes('descriptionItem', { isTerm: true });
+ },
+ };
+ },
+});
diff --git a/app/assets/javascripts/content_editor/extensions/description_list.js b/app/assets/javascripts/content_editor/extensions/description_list.js
new file mode 100644
index 00000000000..a516dfad2b8
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/description_list.js
@@ -0,0 +1,23 @@
+import { Node, mergeAttributes } from '@tiptap/core';
+import { wrappingInputRule } from 'prosemirror-inputrules';
+
+export const inputRegex = /^\s*(<dl>)$/;
+
+export default Node.create({
+ name: 'descriptionList',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ group: 'block list',
+ content: 'descriptionItem+',
+
+ parseHTML() {
+ return [{ tag: 'dl' }];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return ['ul', mergeAttributes(HTMLAttributes, { class: 'dl-content' }), 0];
+ },
+
+ addInputRules() {
+ return [wrappingInputRule(inputRegex, this.type)];
+ },
+});
diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js
index 85191333739..9b2d4c9a062 100644
--- a/app/assets/javascripts/content_editor/services/create_content_editor.js
+++ b/app/assets/javascripts/content_editor/services/create_content_editor.js
@@ -8,6 +8,8 @@ import Bold from '../extensions/bold';
import BulletList from '../extensions/bullet_list';
import Code from '../extensions/code';
import CodeBlockHighlight from '../extensions/code_block_highlight';
+import DescriptionItem from '../extensions/description_item';
+import DescriptionList from '../extensions/description_list';
import Division from '../extensions/division';
import Document from '../extensions/document';
import Dropcursor from '../extensions/dropcursor';
@@ -74,6 +76,8 @@ export const createContentEditor = ({
BulletList,
Code,
CodeBlockHighlight,
+ DescriptionItem,
+ DescriptionList,
Document,
Division,
Dropcursor,
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index d208806b247..bc6d98511f9 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -9,6 +9,8 @@ import Bold from '../extensions/bold';
import BulletList from '../extensions/bullet_list';
import Code from '../extensions/code';
import CodeBlockHighlight from '../extensions/code_block_highlight';
+import DescriptionItem from '../extensions/description_item';
+import DescriptionList from '../extensions/description_list';
import Division from '../extensions/division';
import Emoji from '../extensions/emoji';
import Figure from '../extensions/figure';
@@ -122,6 +124,12 @@ const defaultSerializerConfig = {
state.closeBlock(node);
},
[Division.name]: renderHTMLNode('div'),
+ [DescriptionList.name]: renderHTMLNode('dl', true),
+ [DescriptionItem.name]: (state, node, parent, index) => {
+ if (index === 1) state.ensureNewLine();
+ renderHTMLNode(node.attrs.isTerm ? 'dt' : 'dd')(state, node);
+ if (index === parent.childCount - 1) state.ensureNewLine();
+ },
[Emoji.name]: (state, node) => {
const { name } = node.attrs;
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 47e465fa108..b2327555b45 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -6,6 +6,11 @@ const defaultAttrs = {
th: { colspan: 1, rowspan: 1, colwidth: null },
};
+const ignoreAttrs = {
+ dd: ['isTerm'],
+ dt: ['isTerm'],
+};
+
const tableMap = new WeakMap();
// Source taken from
@@ -118,7 +123,8 @@ export function openTag(tagName, attrs) {
str += Object.entries(attrs || {})
.map(([key, value]) => {
- if (defaultAttrs[tagName]?.[key] === value) return '';
+ if ((ignoreAttrs[tagName] || []).includes(key) || defaultAttrs[tagName]?.[key] === value)
+ return '';
return ` ${key}="${htmlEncode(value?.toString())}"`;
})
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index d03b5cbc26b..625910dbea9 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -3,7 +3,6 @@ import { GlLoadingIcon, GlPagination, GlSprintf } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Mousetrap from 'mousetrap';
import { mapState, mapGetters, mapActions } from 'vuex';
-import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
import api from '~/api';
import {
keysFor,
@@ -55,13 +54,18 @@ import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue';
import HiddenFilesWarning from './hidden_files_warning.vue';
import NoChanges from './no_changes.vue';
-import PreRenderer from './pre_renderer.vue';
import TreeList from './tree_list.vue';
-import VirtualScrollerScrollSync from './virtual_scroller_scroll_sync';
export default {
name: 'DiffsApp',
components: {
+ DynamicScroller: () =>
+ import('vendor/vue-virtual-scroller').then(({ DynamicScroller }) => DynamicScroller),
+ DynamicScrollerItem: () =>
+ import('vendor/vue-virtual-scroller').then(({ DynamicScrollerItem }) => DynamicScrollerItem),
+ PreRenderer: () => import('./pre_renderer.vue').then((PreRenderer) => PreRenderer),
+ VirtualScrollerScrollSync: () =>
+ import('./virtual_scroller_scroll_sync').then((VSSSync) => VSSSync),
CompareVersions,
DiffFile,
NoChanges,
@@ -73,10 +77,6 @@ export default {
PanelResizer,
GlPagination,
GlSprintf,
- DynamicScroller,
- DynamicScrollerItem,
- PreRenderer,
- VirtualScrollerScrollSync,
MrWidgetHowToMergeModal,
},
alerts: {
@@ -189,25 +189,25 @@ export default {
treeWidth,
diffFilesLength: 0,
virtualScrollCurrentIndex: -1,
+ subscribedToVirtualScrollingEvents: false,
};
},
computed: {
- ...mapState({
- isLoading: (state) => state.diffs.isLoading,
- isBatchLoading: (state) => state.diffs.isBatchLoading,
- diffFiles: (state) => state.diffs.diffFiles,
- diffViewType: (state) => state.diffs.diffViewType,
- commit: (state) => state.diffs.commit,
- renderOverflowWarning: (state) => state.diffs.renderOverflowWarning,
- numTotalFiles: (state) => state.diffs.realSize,
- numVisibleFiles: (state) => state.diffs.size,
- plainDiffPath: (state) => state.diffs.plainDiffPath,
- emailPatchPath: (state) => state.diffs.emailPatchPath,
- retrievingBatches: (state) => state.diffs.retrievingBatches,
+ ...mapState('diffs', {
+ numTotalFiles: 'realSize',
+ numVisibleFiles: 'size',
}),
...mapState('diffs', [
'showTreeList',
'isLoading',
+ 'isBatchLoading',
+ 'diffFiles',
+ 'diffViewType',
+ 'commit',
+ 'renderOverflowWarning',
+ 'plainDiffPath',
+ 'emailPatchPath',
+ 'retrievingBatches',
'startVersion',
'latestDiff',
'currentDiffFileId',
@@ -316,6 +316,7 @@ export default {
}
this.adjustView();
+ this.subscribeToVirtualScrollingEvents();
},
isLoading: 'adjustView',
renderFileTree: 'adjustView',
@@ -349,11 +350,6 @@ export default {
this.setHighlightedRow(id.split('diff-content').pop().slice(1));
}
- if (window.gon?.features?.diffsVirtualScrolling) {
- diffsEventHub.$on('scrollToFileHash', this.scrollVirtualScrollerToFileHash);
- diffsEventHub.$on('scrollToIndex', this.scrollVirtualScrollerToIndex);
- }
-
if (window.gon?.features?.diffSettingsUsageData) {
const events = [];
@@ -383,6 +379,8 @@ export default {
queueRedisHllEvents(events);
}
+
+ this.subscribeToVirtualScrollingEvents();
},
beforeCreate() {
diffsApp.instrument();
@@ -611,6 +609,18 @@ export default {
}
}
},
+ subscribeToVirtualScrollingEvents() {
+ if (
+ window.gon?.features?.diffsVirtualScrolling &&
+ this.shouldShow &&
+ !this.subscribedToVirtualScrollingEvents
+ ) {
+ diffsEventHub.$on('scrollToFileHash', this.scrollVirtualScrollerToFileHash);
+ diffsEventHub.$on('scrollToIndex', this.scrollVirtualScrollerToIndex);
+
+ this.subscribedToVirtualScrollingEvents = true;
+ }
+ },
},
minTreeWidth: MIN_TREE_WIDTH,
maxTreeWidth: MAX_TREE_WIDTH,
@@ -672,7 +682,6 @@ export default {
<template v-else-if="renderDiffFiles">
<dynamic-scroller
v-if="isVirtualScrollingEnabled"
- ref="virtualScroller"
:items="diffs"
:min-item-size="70"
:buffer="1000"
diff --git a/app/assets/javascripts/vue_shared/components/code_block.vue b/app/assets/javascripts/vue_shared/components/code_block.vue
index 1928bf6dac5..9856f35c7f6 100644
--- a/app/assets/javascripts/vue_shared/components/code_block.vue
+++ b/app/assets/javascripts/vue_shared/components/code_block.vue
@@ -24,8 +24,13 @@ export default {
return isScrollable ? scrollableStyles : null;
},
},
+ userColorScheme: window.gon.user_color_scheme,
};
</script>
<template>
- <pre class="code-block rounded" :style="styleObject"><code class="d-block">{{ code }}</code></pre>
+ <pre
+ class="code-block rounded code"
+ :class="$options.userColorScheme"
+ :style="styleObject"
+ ><code class="d-block">{{ code }}</code></pre>
</template>
diff --git a/app/assets/stylesheets/components/content_editor.scss b/app/assets/stylesheets/components/content_editor.scss
index 9506eb99b9b..a013d971efb 100644
--- a/app/assets/stylesheets/components/content_editor.scss
+++ b/app/assets/stylesheets/components/content_editor.scss
@@ -1,7 +1,9 @@
.ProseMirror {
td,
th,
- li {
+ li,
+ dd,
+ dt {
:first-child {
margin-bottom: 0 !important;
}
@@ -34,6 +36,20 @@
}
}
}
+
+ .dl-content {
+ width: 100%;
+
+ > li {
+ list-style-type: none;
+ margin-left: $gl-spacing-scale-5;
+
+ &.dl-term {
+ margin: 0;
+ font-weight: 600;
+ }
+ }
+ }
}
.table-creator-grid-item {
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 70070a2a02f..a0682eabf01 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -337,8 +337,6 @@
}
.code-block {
- background: $black;
- color: $gray-darkest;
white-space: pre;
overflow-x: auto;
font-size: 12px;
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index b537a46a6f2..773935f4c76 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -96,15 +96,10 @@
}
form {
- width: 48%;
padding: 0;
border: 0;
background: none;
margin-bottom: $gl-padding;
-
- @include media-breakpoint-down(md) {
- width: 100%;
- }
}
.omniauth-btn {
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index cd0a10bd3a0..013ad3fac87 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -634,17 +634,11 @@ svg {
margin: 0;
}
.login-page .omniauth-container form {
- width: 48%;
padding: 0;
border: 0;
background: none;
margin-bottom: 16px;
}
-@media (max-width: 991.98px) {
- .login-page .omniauth-container form {
- width: 100%;
- }
-}
.login-page .omniauth-container .omniauth-btn {
width: 100%;
}
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index dd8fb1a20b8..95bafe95708 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -311,6 +311,9 @@ module ApplicationSettingsHelper
:throttle_authenticated_api_enabled,
:throttle_authenticated_api_period_in_seconds,
:throttle_authenticated_api_requests_per_period,
+ :throttle_authenticated_git_lfs_enabled,
+ :throttle_authenticated_git_lfs_period_in_seconds,
+ :throttle_authenticated_git_lfs_requests_per_period,
:throttle_authenticated_web_enabled,
:throttle_authenticated_web_period_in_seconds,
:throttle_authenticated_web_requests_per_period,
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 869e9ee5bea..0ffd46ab88f 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -497,6 +497,14 @@ class ApplicationSetting < ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :throttle_authenticated_git_lfs_requests_per_period,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
+ validates :throttle_authenticated_git_lfs_period_in_seconds,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
validates :throttle_authenticated_web_requests_per_period,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index c194bafeee3..5d804ebcdab 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -163,6 +163,9 @@ module ApplicationSettingImplementation
throttle_authenticated_api_enabled: false,
throttle_authenticated_api_period_in_seconds: 3600,
throttle_authenticated_api_requests_per_period: 7200,
+ throttle_authenticated_git_lfs_enabled: false,
+ throttle_authenticated_git_lfs_period_in_seconds: 60,
+ throttle_authenticated_git_lfs_requests_per_period: 1000,
throttle_authenticated_web_enabled: false,
throttle_authenticated_web_period_in_seconds: 3600,
throttle_authenticated_web_requests_per_period: 7200,
diff --git a/app/views/admin/application_settings/_git_lfs_limits.html.haml b/app/views/admin/application_settings/_git_lfs_limits.html.haml
new file mode 100644
index 00000000000..de5a2ceaa3d
--- /dev/null
+++ b/app/views/admin/application_settings/_git_lfs_limits.html.haml
@@ -0,0 +1,21 @@
+= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-git-lfs-limits-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ %h5
+ = _('Authenticated Git LFS request rate limit')
+ .form-group
+ .form-check
+ = f.check_box :throttle_authenticated_git_lfs_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_git_lfs_checkbox' }
+ = f.label :throttle_authenticated_git_lfs_enabled, class: 'form-check-label gl-font-weight-bold' do
+ = _('Enable authenticated Git LFS request rate limit')
+ %span.form-text.gl-text-gray-600
+ = _('Helps reduce request volume (for example, from crawlers or abusive bots)')
+ .form-group
+ = f.label :throttle_authenticated_git_lfs_requests_per_period, _('Max authenticated Git LFS requests per period per user'), class: 'gl-font-weight-bold'
+ = f.number_field :throttle_authenticated_git_lfs_requests_per_period, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :throttle_authenticated_git_lfs_period_in_seconds, _('Authenticated Git LFS rate limit period in seconds'), class: 'gl-font-weight-bold'
+ = f.number_field :throttle_authenticated_git_lfs_period_in_seconds, class: 'form-control gl-form-input'
+
+ = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 0e9dcb23dcb..0fb07d2ef17 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -35,6 +35,18 @@
.settings-content
= render 'package_registry_limits'
+%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
+ = _('Git LFS Rate Limits')
+ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure specific limits for Git LFS requests that supersede the general user and IP rate limits.')
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/git_lfs_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
+ .settings-content
+ = render 'git_lfs_limits'
+
%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_content' } }
.settings-header
%h4
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 8b54b735205..1752a43b032 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -7,10 +7,10 @@
.d-flex.justify-content-between.flex-wrap
- providers.each do |provider|
- has_icon = provider_has_icon?(provider)
- = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", class: "btn gl-button btn-default omniauth-btn oauth-login #{qa_class_for_provider(provider)}" do
+ = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", class: "btn gl-button btn-default omniauth-btn oauth-login #{qa_class_for_provider(provider)}", form: { class: 'gl-w-full' } do
- if has_icon
= provider_image_tag(provider)
- %span
+ %span.gl-button-text
= label_for_provider(provider)
- unless hide_remember_me
%fieldset.remember-me
diff --git a/db/migrate/20210830085837_add_throttle_authenticated_git_lfs_columns.rb b/db/migrate/20210830085837_add_throttle_authenticated_git_lfs_columns.rb
new file mode 100644
index 00000000000..bc515bc061c
--- /dev/null
+++ b/db/migrate/20210830085837_add_throttle_authenticated_git_lfs_columns.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddThrottleAuthenticatedGitLfsColumns < ActiveRecord::Migration[6.1]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ add_column :application_settings, :throttle_authenticated_git_lfs_requests_per_period, :integer, default: 1000, null: false
+ add_column :application_settings, :throttle_authenticated_git_lfs_period_in_seconds, :integer, default: 60, null: false
+ add_column :application_settings, :throttle_authenticated_git_lfs_enabled, :boolean, default: false, null: false
+ end
+ end
+
+ def down
+ with_lock_retries do
+ remove_column :application_settings, :throttle_authenticated_git_lfs_requests_per_period
+ remove_column :application_settings, :throttle_authenticated_git_lfs_period_in_seconds
+ remove_column :application_settings, :throttle_authenticated_git_lfs_enabled, :boolean
+ end
+ end
+end
diff --git a/db/schema_migrations/20210830085837 b/db/schema_migrations/20210830085837
new file mode 100644
index 00000000000..590ff1fe13b
--- /dev/null
+++ b/db/schema_migrations/20210830085837
@@ -0,0 +1 @@
+cf1a51194961500cb63d848afb1d5ebbf2ef77f54d60957e92c9dd6a667913ea \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 67438b2dbaf..6467d3d19df 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9614,6 +9614,9 @@ CREATE TABLE application_settings (
throttle_authenticated_files_api_period_in_seconds integer DEFAULT 15 NOT NULL,
throttle_unauthenticated_files_api_enabled boolean DEFAULT false NOT NULL,
throttle_authenticated_files_api_enabled boolean DEFAULT false NOT NULL,
+ throttle_authenticated_git_lfs_requests_per_period integer DEFAULT 1000 NOT NULL,
+ throttle_authenticated_git_lfs_period_in_seconds integer DEFAULT 60 NOT NULL,
+ throttle_authenticated_git_lfs_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 9765e6956ad..7d3e9c55b0c 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -78,6 +78,16 @@ This setting limits the request rate on the Packages API per user or IP. For mor
- **Default rate limit**: Disabled by default.
+### Git LFS
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68642) in GitLab 14.3.
+
+This setting limits the request rate on the [Git LFS](../topics/git/lfs/index.md)
+requests per user. For more information, read
+[GitLab Git Large File Storage (LFS) Administration](../administration/lfs/index.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/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d83b4da98ba..0cac638a4b3 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -631,7 +631,7 @@ Input type: `ApiFuzzingCiConfigurationCreateInput`
| <a id="mutationapifuzzingciconfigurationcreateauthusername"></a>`authUsername` | [`String`](#string) | CI variable containing the username for authenticating with the target API. |
| <a id="mutationapifuzzingciconfigurationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationapifuzzingciconfigurationcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
-| <a id="mutationapifuzzingciconfigurationcreatescanmode"></a>`scanMode` | [`ApiFuzzingScanMode!`](#apifuzzingscanmode) | The mode for API fuzzing scans. |
+| <a id="mutationapifuzzingciconfigurationcreatescanmode"></a>`scanMode` | [`ApiFuzzingScanMode!`](#apifuzzingscanmode) | Mode for API fuzzing scans. |
| <a id="mutationapifuzzingciconfigurationcreatescanprofile"></a>`scanProfile` | [`String`](#string) | Name of a default profile to use for scanning. Ex: Quick-10. |
| <a id="mutationapifuzzingciconfigurationcreatetarget"></a>`target` | [`String!`](#string) | URL for the target of API fuzzing scans. |
@@ -642,7 +642,7 @@ Input type: `ApiFuzzingCiConfigurationCreateInput`
| <a id="mutationapifuzzingciconfigurationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationapifuzzingciconfigurationcreateconfigurationyaml"></a>`configurationYaml` | [`String`](#string) | A YAML snippet that can be inserted into the project's `.gitlab-ci.yml` to set up API fuzzing scans. |
| <a id="mutationapifuzzingciconfigurationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationapifuzzingciconfigurationcreategitlabciyamleditpath"></a>`gitlabCiYamlEditPath` | [`String`](#string) | The location at which the project's `.gitlab-ci.yml` file can be edited in the browser. |
+| <a id="mutationapifuzzingciconfigurationcreategitlabciyamleditpath"></a>`gitlabCiYamlEditPath` | [`String`](#string) | Location at which the project's `.gitlab-ci.yml` file can be edited in the browser. |
### `Mutation.awardEmojiAdd`
@@ -760,10 +760,10 @@ Input type: `BoardListUpdateLimitMetricsInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationboardlistupdatelimitmetricsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationboardlistupdatelimitmetricslimitmetric"></a>`limitMetric` | [`ListLimitMetric`](#listlimitmetric) | The new limit metric type for the list. |
-| <a id="mutationboardlistupdatelimitmetricslistid"></a>`listId` | [`ListID!`](#listid) | The global ID of the list. |
-| <a id="mutationboardlistupdatelimitmetricsmaxissuecount"></a>`maxIssueCount` | [`Int`](#int) | The new maximum issue count limit. |
-| <a id="mutationboardlistupdatelimitmetricsmaxissueweight"></a>`maxIssueWeight` | [`Int`](#int) | The new maximum issue weight limit. |
+| <a id="mutationboardlistupdatelimitmetricslimitmetric"></a>`limitMetric` | [`ListLimitMetric`](#listlimitmetric) | New limit metric type for the list. |
+| <a id="mutationboardlistupdatelimitmetricslistid"></a>`listId` | [`ListID!`](#listid) | Global ID of the list. |
+| <a id="mutationboardlistupdatelimitmetricsmaxissuecount"></a>`maxIssueCount` | [`Int`](#int) | New maximum issue count limit. |
+| <a id="mutationboardlistupdatelimitmetricsmaxissueweight"></a>`maxIssueWeight` | [`Int`](#int) | New maximum issue weight limit. |
#### Fields
@@ -771,7 +771,7 @@ Input type: `BoardListUpdateLimitMetricsInput`
| ---- | ---- | ----------- |
| <a id="mutationboardlistupdatelimitmetricsclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationboardlistupdatelimitmetricserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationboardlistupdatelimitmetricslist"></a>`list` | [`BoardList`](#boardlist) | The updated list. |
+| <a id="mutationboardlistupdatelimitmetricslist"></a>`list` | [`BoardList`](#boardlist) | Updated list. |
### `Mutation.bulkEnableDevopsAdoptionNamespaces`
@@ -1149,7 +1149,7 @@ Input type: `CreateComplianceFrameworkInput`
| ---- | ---- | ----------- |
| <a id="mutationcreatecomplianceframeworkclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatecomplianceframeworkerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationcreatecomplianceframeworkframework"></a>`framework` | [`ComplianceFramework`](#complianceframework) | The created compliance framework. |
+| <a id="mutationcreatecomplianceframeworkframework"></a>`framework` | [`ComplianceFramework`](#complianceframework) | Created compliance framework. |
### `Mutation.createCustomEmoji`
@@ -1204,17 +1204,17 @@ Input type: `CreateEpicInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="mutationcreateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | The IDs of labels to be added to the epic. |
+| <a id="mutationcreateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. |
| <a id="mutationcreateepicclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreateepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
-| <a id="mutationcreateepicdescription"></a>`description` | [`String`](#string) | The description of the epic. |
-| <a id="mutationcreateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | The end date of the epic. |
+| <a id="mutationcreateepicdescription"></a>`description` | [`String`](#string) | Description of the epic. |
+| <a id="mutationcreateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | End date of the epic. |
| <a id="mutationcreateepicduedateisfixed"></a>`dueDateIsFixed` | [`Boolean`](#boolean) | Indicates end date should be sourced from due_date_fixed field not the issue milestones. |
-| <a id="mutationcreateepicgrouppath"></a>`groupPath` | [`ID!`](#id) | The group the epic to mutate is in. |
-| <a id="mutationcreateepicremovelabelids"></a>`removeLabelIds` | [`[ID!]`](#id) | The IDs of labels to be removed from the epic. |
-| <a id="mutationcreateepicstartdatefixed"></a>`startDateFixed` | [`String`](#string) | The start date of the epic. |
+| <a id="mutationcreateepicgrouppath"></a>`groupPath` | [`ID!`](#id) | Group the epic to mutate is in. |
+| <a id="mutationcreateepicremovelabelids"></a>`removeLabelIds` | [`[ID!]`](#id) | IDs of labels to be removed from the epic. |
+| <a id="mutationcreateepicstartdatefixed"></a>`startDateFixed` | [`String`](#string) | Start date of the epic. |
| <a id="mutationcreateepicstartdateisfixed"></a>`startDateIsFixed` | [`Boolean`](#boolean) | Indicates start date should be sourced from start_date_fixed field not the issue milestones. |
-| <a id="mutationcreateepictitle"></a>`title` | [`String`](#string) | The title of the epic. |
+| <a id="mutationcreateepictitle"></a>`title` | [`String`](#string) | Title of the epic. |
#### Fields
@@ -1417,7 +1417,7 @@ Input type: `DastOnDemandScanCreateInput`
| <a id="mutationdastondemandscancreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastondemandscancreatedastscannerprofileid"></a>`dastScannerProfileId` | [`DastScannerProfileID`](#dastscannerprofileid) | ID of the scanner profile to be used for the scan. |
| <a id="mutationdastondemandscancreatedastsiteprofileid"></a>`dastSiteProfileId` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be used for the scan. |
-| <a id="mutationdastondemandscancreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. |
+| <a id="mutationdastondemandscancreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. |
#### Fields
@@ -1435,13 +1435,13 @@ Input type: `DastProfileCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="mutationdastprofilecreatebranchname"></a>`branchName` | [`String`](#string) | The associated branch. |
+| <a id="mutationdastprofilecreatebranchname"></a>`branchName` | [`String`](#string) | Associated branch. |
| <a id="mutationdastprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastprofilecreatedastscannerprofileid"></a>`dastScannerProfileId` | [`DastScannerProfileID!`](#dastscannerprofileid) | ID of the scanner profile to be associated. |
| <a id="mutationdastprofilecreatedastsiteprofileid"></a>`dastSiteProfileId` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be associated. |
-| <a id="mutationdastprofilecreatedescription"></a>`description` | [`String`](#string) | The description of the profile. Defaults to an empty string. |
-| <a id="mutationdastprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the profile belongs to. |
-| <a id="mutationdastprofilecreatename"></a>`name` | [`String!`](#string) | The name of the profile. |
+| <a id="mutationdastprofilecreatedescription"></a>`description` | [`String`](#string) | Description of the profile. Defaults to an empty string. |
+| <a id="mutationdastprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the profile belongs to. |
+| <a id="mutationdastprofilecreatename"></a>`name` | [`String!`](#string) | Name of the profile. |
| <a id="mutationdastprofilecreaterunaftercreate"></a>`runAfterCreate` | [`Boolean`](#boolean) | Run scan using profile after creation. Defaults to false. |
#### Fields
@@ -1449,9 +1449,9 @@ Input type: `DastProfileCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastprofilecreatedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | The created profile. |
+| <a id="mutationdastprofilecreatedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | Created profile. |
| <a id="mutationdastprofilecreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationdastprofilecreatepipelineurl"></a>`pipelineUrl` | [`String`](#string) | The URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`. |
+| <a id="mutationdastprofilecreatepipelineurl"></a>`pipelineUrl` | [`String`](#string) | URL of the pipeline that was created. Requires `runAfterCreate` to be set to `true`. |
### `Mutation.dastProfileDelete`
@@ -1499,14 +1499,14 @@ Input type: `DastProfileUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="mutationdastprofileupdatebranchname"></a>`branchName` | [`String`](#string) | The associated branch. |
+| <a id="mutationdastprofileupdatebranchname"></a>`branchName` | [`String`](#string) | Associated branch. |
| <a id="mutationdastprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastprofileupdatedastscannerprofileid"></a>`dastScannerProfileId` | [`DastScannerProfileID`](#dastscannerprofileid) | ID of the scanner profile to be associated. |
| <a id="mutationdastprofileupdatedastsiteprofileid"></a>`dastSiteProfileId` | [`DastSiteProfileID`](#dastsiteprofileid) | ID of the site profile to be associated. |
-| <a id="mutationdastprofileupdatedescription"></a>`description` | [`String`](#string) | The description of the profile. Defaults to an empty string. |
-| <a id="mutationdastprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the profile belongs to. |
+| <a id="mutationdastprofileupdatedescription"></a>`description` | [`String`](#string) | Description of the profile. Defaults to an empty string. |
+| <a id="mutationdastprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the profile belongs to. |
| <a id="mutationdastprofileupdateid"></a>`id` | [`DastProfileID!`](#dastprofileid) | ID of the profile to be deleted. |
-| <a id="mutationdastprofileupdatename"></a>`name` | [`String`](#string) | The name of the profile. |
+| <a id="mutationdastprofileupdatename"></a>`name` | [`String`](#string) | Name of the profile. |
| <a id="mutationdastprofileupdaterunafterupdate"></a>`runAfterUpdate` | [`Boolean`](#boolean) | Run scan using profile after update. Defaults to false. |
#### Fields
@@ -1514,7 +1514,7 @@ Input type: `DastProfileUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastprofileupdatedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | The updated profile. |
+| <a id="mutationdastprofileupdatedastprofile"></a>`dastProfile` | [`DastProfile`](#dastprofile) | Updated profile. |
| <a id="mutationdastprofileupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdastprofileupdatepipelineurl"></a>`pipelineUrl` | [`String`](#string) | The URL of the pipeline that was created. Requires the input argument `runAfterUpdate` to be set to `true` when calling the mutation, otherwise no pipeline will be created. |
@@ -1527,12 +1527,12 @@ Input type: `DastScannerProfileCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastscannerprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastscannerprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the scanner profile belongs to. |
-| <a id="mutationdastscannerprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | The name of the scanner profile. |
+| <a id="mutationdastscannerprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the scanner profile belongs to. |
+| <a id="mutationdastscannerprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | Name of the scanner profile. |
| <a id="mutationdastscannerprofilecreatescantype"></a>`scanType` | [`DastScanTypeEnum`](#dastscantypeenum) | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. |
| <a id="mutationdastscannerprofilecreateshowdebugmessages"></a>`showDebugMessages` | [`Boolean`](#boolean) | Indicates if debug messages should be included in DAST console output. True to include the debug messages. |
-| <a id="mutationdastscannerprofilecreatespidertimeout"></a>`spiderTimeout` | [`Int`](#int) | The maximum number of minutes allowed for the spider to traverse the site. |
-| <a id="mutationdastscannerprofilecreatetargettimeout"></a>`targetTimeout` | [`Int`](#int) | The maximum number of seconds allowed for the site under test to respond to a request. |
+| <a id="mutationdastscannerprofilecreatespidertimeout"></a>`spiderTimeout` | [`Int`](#int) | Maximum number of minutes allowed for the spider to traverse the site. |
+| <a id="mutationdastscannerprofilecreatetargettimeout"></a>`targetTimeout` | [`Int`](#int) | Maximum number of seconds allowed for the site under test to respond to a request. |
| <a id="mutationdastscannerprofilecreateuseajaxspider"></a>`useAjaxSpider` | [`Boolean`](#boolean) | Indicates if the AJAX spider should be used to crawl the target site. True to run the AJAX spider in addition to the traditional spider, and false to run only the traditional spider. |
#### Fields
@@ -1571,13 +1571,13 @@ Input type: `DastScannerProfileUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastscannerprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastscannerprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the scanner profile belongs to. |
+| <a id="mutationdastscannerprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the scanner profile belongs to. |
| <a id="mutationdastscannerprofileupdateid"></a>`id` | [`DastScannerProfileID!`](#dastscannerprofileid) | ID of the scanner profile to be updated. |
-| <a id="mutationdastscannerprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | The name of the scanner profile. |
+| <a id="mutationdastscannerprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | Name of the scanner profile. |
| <a id="mutationdastscannerprofileupdatescantype"></a>`scanType` | [`DastScanTypeEnum`](#dastscantypeenum) | Indicates the type of DAST scan that will run. Either a Passive Scan or an Active Scan. |
| <a id="mutationdastscannerprofileupdateshowdebugmessages"></a>`showDebugMessages` | [`Boolean`](#boolean) | Indicates if debug messages should be included in DAST console output. True to include the debug messages. |
-| <a id="mutationdastscannerprofileupdatespidertimeout"></a>`spiderTimeout` | [`Int!`](#int) | The maximum number of minutes allowed for the spider to traverse the site. |
-| <a id="mutationdastscannerprofileupdatetargettimeout"></a>`targetTimeout` | [`Int!`](#int) | The maximum number of seconds allowed for the site under test to respond to a request. |
+| <a id="mutationdastscannerprofileupdatespidertimeout"></a>`spiderTimeout` | [`Int!`](#int) | Maximum number of minutes allowed for the spider to traverse the site. |
+| <a id="mutationdastscannerprofileupdatetargettimeout"></a>`targetTimeout` | [`Int!`](#int) | Maximum number of seconds allowed for the site under test to respond to a request. |
| <a id="mutationdastscannerprofileupdateuseajaxspider"></a>`useAjaxSpider` | [`Boolean`](#boolean) | Indicates if the AJAX spider should be used to crawl the target site. True to run the AJAX spider in addition to the traditional spider, and false to run only the traditional spider. |
#### Fields
@@ -1598,12 +1598,12 @@ Input type: `DastSiteProfileCreateInput`
| ---- | ---- | ----------- |
| <a id="mutationdastsiteprofilecreateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. |
| <a id="mutationdastsiteprofilecreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastsiteprofilecreateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. Defaults to `[]`. |
-| <a id="mutationdastsiteprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. |
-| <a id="mutationdastsiteprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | The name of the site profile. |
+| <a id="mutationdastsiteprofilecreateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | URLs to skip during an authenticated scan. Defaults to `[]`. |
+| <a id="mutationdastsiteprofilecreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. |
+| <a id="mutationdastsiteprofilecreateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. |
| <a id="mutationdastsiteprofilecreaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. |
-| <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. |
-| <a id="mutationdastsiteprofilecreatetargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be scanned. |
+| <a id="mutationdastsiteprofilecreatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. |
+| <a id="mutationdastsiteprofilecreatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. |
#### Fields
@@ -1622,7 +1622,7 @@ Input type: `DastSiteProfileDeleteInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastsiteprofiledeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastsiteprofiledeletefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. |
+| <a id="mutationdastsiteprofiledeletefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. |
| <a id="mutationdastsiteprofiledeleteid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be deleted. |
#### Fields
@@ -1642,13 +1642,13 @@ Input type: `DastSiteProfileUpdateInput`
| ---- | ---- | ----------- |
| <a id="mutationdastsiteprofileupdateauth"></a>`auth` | [`DastSiteProfileAuthInput`](#dastsiteprofileauthinput) | Parameters for authentication. |
| <a id="mutationdastsiteprofileupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastsiteprofileupdateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | The URLs to skip during an authenticated scan. |
-| <a id="mutationdastsiteprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. |
+| <a id="mutationdastsiteprofileupdateexcludedurls"></a>`excludedUrls` | [`[String!]`](#string) | URLs to skip during an authenticated scan. |
+| <a id="mutationdastsiteprofileupdatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. |
| <a id="mutationdastsiteprofileupdateid"></a>`id` | [`DastSiteProfileID!`](#dastsiteprofileid) | ID of the site profile to be updated. |
-| <a id="mutationdastsiteprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | The name of the site profile. |
+| <a id="mutationdastsiteprofileupdateprofilename"></a>`profileName` | [`String!`](#string) | Name of the site profile. |
| <a id="mutationdastsiteprofileupdaterequestheaders"></a>`requestHeaders` | [`String`](#string) | Comma-separated list of request header names and values to be added to every request made by DAST. |
-| <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | The type of target to be scanned. |
-| <a id="mutationdastsiteprofileupdatetargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be scanned. |
+| <a id="mutationdastsiteprofileupdatetargettype"></a>`targetType` | [`DastTargetTypeEnum`](#dasttargettypeenum) | Type of target to be scanned. |
+| <a id="mutationdastsiteprofileupdatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be scanned. |
#### Fields
@@ -1667,8 +1667,8 @@ Input type: `DastSiteTokenCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastsitetokencreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastsitetokencreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site token belongs to. |
-| <a id="mutationdastsitetokencreatetargeturl"></a>`targetUrl` | [`String`](#string) | The URL of the target to be validated. |
+| <a id="mutationdastsitetokencreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site token belongs to. |
+| <a id="mutationdastsitetokencreatetargeturl"></a>`targetUrl` | [`String`](#string) | URL of the target to be validated. |
#### Fields
@@ -1677,7 +1677,7 @@ Input type: `DastSiteTokenCreateInput`
| <a id="mutationdastsitetokencreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastsitetokencreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdastsitetokencreateid"></a>`id` | [`DastSiteTokenID`](#dastsitetokenid) | ID of the site token. |
-| <a id="mutationdastsitetokencreatestatus"></a>`status` | [`DastSiteProfileValidationStatusEnum`](#dastsiteprofilevalidationstatusenum) | The current validation status of the target. |
+| <a id="mutationdastsitetokencreatestatus"></a>`status` | [`DastSiteProfileValidationStatusEnum`](#dastsiteprofilevalidationstatusenum) | Current validation status of the target. |
| <a id="mutationdastsitetokencreatetoken"></a>`token` | [`String`](#string) | Token string. |
### `Mutation.dastSiteValidationCreate`
@@ -1690,9 +1690,9 @@ Input type: `DastSiteValidationCreateInput`
| ---- | ---- | ----------- |
| <a id="mutationdastsitevalidationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastsitevalidationcreatedastsitetokenid"></a>`dastSiteTokenId` | [`DastSiteTokenID!`](#dastsitetokenid) | ID of the site token. |
-| <a id="mutationdastsitevalidationcreatefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site profile belongs to. |
-| <a id="mutationdastsitevalidationcreatestrategy"></a>`strategy` | [`DastSiteValidationStrategyEnum`](#dastsitevalidationstrategyenum) | The validation strategy to be used. |
-| <a id="mutationdastsitevalidationcreatevalidationpath"></a>`validationPath` | [`String!`](#string) | The path to be requested during validation. |
+| <a id="mutationdastsitevalidationcreatefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site profile belongs to. |
+| <a id="mutationdastsitevalidationcreatestrategy"></a>`strategy` | [`DastSiteValidationStrategyEnum`](#dastsitevalidationstrategyenum) | Validation strategy to be used. |
+| <a id="mutationdastsitevalidationcreatevalidationpath"></a>`validationPath` | [`String!`](#string) | Path to be requested during validation. |
#### Fields
@@ -1701,7 +1701,7 @@ Input type: `DastSiteValidationCreateInput`
| <a id="mutationdastsitevalidationcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationdastsitevalidationcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationdastsitevalidationcreateid"></a>`id` | [`DastSiteValidationID`](#dastsitevalidationid) | ID of the site validation. |
-| <a id="mutationdastsitevalidationcreatestatus"></a>`status` | [`DastSiteProfileValidationStatusEnum`](#dastsiteprofilevalidationstatusenum) | The current validation status. |
+| <a id="mutationdastsitevalidationcreatestatus"></a>`status` | [`DastSiteProfileValidationStatusEnum`](#dastsiteprofilevalidationstatusenum) | Current validation status. |
### `Mutation.dastSiteValidationRevoke`
@@ -1712,7 +1712,7 @@ Input type: `DastSiteValidationRevokeInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdastsitevalidationrevokeclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdastsitevalidationrevokefullpath"></a>`fullPath` | [`ID!`](#id) | The project the site validation belongs to. |
+| <a id="mutationdastsitevalidationrevokefullpath"></a>`fullPath` | [`ID!`](#id) | Project the site validation belongs to. |
| <a id="mutationdastsitevalidationrevokenormalizedtargeturl"></a>`normalizedTargetUrl` | [`String!`](#string) | Normalized URL of the target to be revoked. |
#### Fields
@@ -1851,7 +1851,7 @@ Input type: `DestroyComplianceFrameworkInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationdestroycomplianceframeworkclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationdestroycomplianceframeworkid"></a>`id` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | The global ID of the compliance framework to destroy. |
+| <a id="mutationdestroycomplianceframeworkid"></a>`id` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | Global ID of the compliance framework to destroy. |
#### Fields
@@ -2144,7 +2144,7 @@ Input type: `EpicBoardCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationepicboardcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationepicboardcreateepicboard"></a>`epicBoard` | [`EpicBoard`](#epicboard) | The created epic board. |
+| <a id="mutationepicboardcreateepicboard"></a>`epicBoard` | [`EpicBoard`](#epicboard) | Created epic board. |
| <a id="mutationepicboardcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.epicBoardListCreate`
@@ -2187,7 +2187,7 @@ Input type: `EpicBoardListDestroyInput`
| ---- | ---- | ----------- |
| <a id="mutationepicboardlistdestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationepicboardlistdestroyerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
-| <a id="mutationepicboardlistdestroylist"></a>`list` | [`EpicList`](#epiclist) | The epic board list. `null` if the board was destroyed successfully. |
+| <a id="mutationepicboardlistdestroylist"></a>`list` | [`EpicList`](#epiclist) | Epic board list. `null` if the board was destroyed successfully. |
### `Mutation.epicBoardUpdate`
@@ -2200,7 +2200,7 @@ Input type: `EpicBoardUpdateInput`
| <a id="mutationepicboardupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationepicboardupdatehidebackloglist"></a>`hideBacklogList` | [`Boolean`](#boolean) | Whether or not backlog list is hidden. |
| <a id="mutationepicboardupdatehideclosedlist"></a>`hideClosedList` | [`Boolean`](#boolean) | Whether or not closed list is hidden. |
-| <a id="mutationepicboardupdateid"></a>`id` | [`BoardsEpicBoardID!`](#boardsepicboardid) | The epic board global ID. |
+| <a id="mutationepicboardupdateid"></a>`id` | [`BoardsEpicBoardID!`](#boardsepicboardid) | Epic board global ID. |
| <a id="mutationepicboardupdatelabelids"></a>`labelIds` | [`[LabelID!]`](#labelid) | IDs of labels to be added to the board. |
| <a id="mutationepicboardupdatelabels"></a>`labels` | [`[String!]`](#string) | Labels of the issue. |
| <a id="mutationepicboardupdatename"></a>`name` | [`String`](#string) | Board name. |
@@ -2210,7 +2210,7 @@ Input type: `EpicBoardUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationepicboardupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationepicboardupdateepicboard"></a>`epicBoard` | [`EpicBoard`](#epicboard) | The updated epic board. |
+| <a id="mutationepicboardupdateepicboard"></a>`epicBoard` | [`EpicBoard`](#epicboard) | Updated epic board. |
| <a id="mutationepicboardupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.epicMoveList`
@@ -2234,7 +2234,7 @@ Input type: `EpicMoveListInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationepicmovelistclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationepicmovelistepic"></a>`epic` | [`Epic`](#epic) | The epic after mutation. |
+| <a id="mutationepicmovelistepic"></a>`epic` | [`Epic`](#epic) | Epic after mutation. |
| <a id="mutationepicmovelisterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.epicSetSubscription`
@@ -4099,7 +4099,7 @@ Input type: `UpdateBoardEpicUserPreferencesInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="mutationupdateboardepicuserpreferencesboardid"></a>`boardId` | [`BoardID!`](#boardid) | The board global ID. |
+| <a id="mutationupdateboardepicuserpreferencesboardid"></a>`boardId` | [`BoardID!`](#boardid) | Board global ID. |
| <a id="mutationupdateboardepicuserpreferencesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationupdateboardepicuserpreferencescollapsed"></a>`collapsed` | [`Boolean!`](#boolean) | Whether the epic should be collapsed in the board. |
| <a id="mutationupdateboardepicuserpreferencesepicid"></a>`epicId` | [`EpicID!`](#epicid) | ID of an epic to set preferences for. |
@@ -4142,7 +4142,7 @@ Input type: `UpdateComplianceFrameworkInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationupdatecomplianceframeworkclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationupdatecomplianceframeworkid"></a>`id` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | The global ID of the compliance framework to update. |
+| <a id="mutationupdatecomplianceframeworkid"></a>`id` | [`ComplianceManagementFrameworkID!`](#compliancemanagementframeworkid) | Global ID of the compliance framework to update. |
| <a id="mutationupdatecomplianceframeworkparams"></a>`params` | [`ComplianceFrameworkInput!`](#complianceframeworkinput) | Parameters to update the compliance framework with. |
#### Fields
@@ -4150,7 +4150,7 @@ Input type: `UpdateComplianceFrameworkInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationupdatecomplianceframeworkclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
-| <a id="mutationupdatecomplianceframeworkcomplianceframework"></a>`complianceFramework` | [`ComplianceFramework`](#complianceframework) | The compliance framework after mutation. |
+| <a id="mutationupdatecomplianceframeworkcomplianceframework"></a>`complianceFramework` | [`ComplianceFramework`](#complianceframework) | Compliance framework after mutation. |
| <a id="mutationupdatecomplianceframeworkerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.updateContainerExpirationPolicy`
@@ -4186,19 +4186,19 @@ Input type: `UpdateEpicInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="mutationupdateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | The IDs of labels to be added to the epic. |
+| <a id="mutationupdateepicaddlabelids"></a>`addLabelIds` | [`[ID!]`](#id) | IDs of labels to be added to the epic. |
| <a id="mutationupdateepicclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationupdateepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
-| <a id="mutationupdateepicdescription"></a>`description` | [`String`](#string) | The description of the epic. |
-| <a id="mutationupdateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | The end date of the epic. |
+| <a id="mutationupdateepicdescription"></a>`description` | [`String`](#string) | Description of the epic. |
+| <a id="mutationupdateepicduedatefixed"></a>`dueDateFixed` | [`String`](#string) | End date of the epic. |
| <a id="mutationupdateepicduedateisfixed"></a>`dueDateIsFixed` | [`Boolean`](#boolean) | Indicates end date should be sourced from due_date_fixed field not the issue milestones. |
-| <a id="mutationupdateepicgrouppath"></a>`groupPath` | [`ID!`](#id) | The group the epic to mutate is in. |
+| <a id="mutationupdateepicgrouppath"></a>`groupPath` | [`ID!`](#id) | Group the epic to mutate is in. |
| <a id="mutationupdateepiciid"></a>`iid` | [`ID!`](#id) | The IID of the epic to mutate. |
-| <a id="mutationupdateepicremovelabelids"></a>`removeLabelIds` | [`[ID!]`](#id) | The IDs of labels to be removed from the epic. |
-| <a id="mutationupdateepicstartdatefixed"></a>`startDateFixed` | [`String`](#string) | The start date of the epic. |
+| <a id="mutationupdateepicremovelabelids"></a>`removeLabelIds` | [`[ID!]`](#id) | IDs of labels to be removed from the epic. |
+| <a id="mutationupdateepicstartdatefixed"></a>`startDateFixed` | [`String`](#string) | Start date of the epic. |
| <a id="mutationupdateepicstartdateisfixed"></a>`startDateIsFixed` | [`Boolean`](#boolean) | Indicates start date should be sourced from start_date_fixed field not the issue milestones. |
| <a id="mutationupdateepicstateevent"></a>`stateEvent` | [`EpicStateEvent`](#epicstateevent) | State event for the epic. |
-| <a id="mutationupdateepictitle"></a>`title` | [`String`](#string) | The title of the epic. |
+| <a id="mutationupdateepictitle"></a>`title` | [`String`](#string) | Title of the epic. |
#### Fields
diff --git a/doc/api/members.md b/doc/api/members.md
index 4b383efd792..fc02103b578 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -552,7 +552,7 @@ Example response:
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon",
"web_url": "http://192.168.1.8:3000/root",
- "expires_at": "2012-10-22T14:13:35Z",
+ "expires_at": "2012-10-22",
"access_level": 40,
"email": "john@example.com",
"override": false
diff --git a/doc/architecture/blueprints/container_registry_metadata_database/index.md b/doc/architecture/blueprints/container_registry_metadata_database/index.md
index b71517de061..f52635baf7b 100644
--- a/doc/architecture/blueprints/container_registry_metadata_database/index.md
+++ b/doc/architecture/blueprints/container_registry_metadata_database/index.md
@@ -77,12 +77,12 @@ The single entrypoint for the registry is the [HTTP API](https://gitlab.com/gitl
| Operation | UI | Background | Observations |
| ------------------------------------------------------------ | ------------------ | ------------------------ | ------------------------------------------------------------ |
-| [Check API version](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#api-version-check) | :heavy_check_mark: | :heavy_check_mark: | Used globally to ensure that the registry supports the Docker Distribution V2 API, as well as for identifying whether GitLab Rails is talking to the GitLab Container Registry or a third-party one (used to toggle features only available in the former). |
-| [List repository tags](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-image-tags) | :heavy_check_mark: | :heavy_check_mark: | Used to list and show tags in the UI. Used to list tags in the background for [cleanup policies](../../../user/packages/container_registry/#cleanup-policy) and [Geo replication](../../../administration/geo/replication/docker_registry.md). |
-| [Check if manifest exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-manifests) | :heavy_check_mark: | :heavy_multiplication_x: | Used to get the digest of a manifest by tag. This is then used to pull the manifest and show the tag details in the UI. |
-| [Pull manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-an-image-manifest) | :heavy_check_mark: | :heavy_multiplication_x: | Used to show the image size and the manifest digest in the tag details UI. |
-| [Pull blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-a-layer) | :heavy_check_mark: | :heavy_multiplication_x: | Used to show the configuration digest and the creation date in the tag details UI. |
-| [Delete tag](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-a-tag) | :heavy_check_mark: | :heavy_check_mark: | Used to delete a tag from the UI and in background (cleanup policies). |
+| [Check API version](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#api-version-check) | **{check-circle}** Yes | **{check-circle}** Yes | Used globally to ensure that the registry supports the Docker Distribution V2 API, as well as for identifying whether GitLab Rails is talking to the GitLab Container Registry or a third-party one (used to toggle features only available in the former). |
+| [List repository tags](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-image-tags) | **{check-circle}** Yes | **{check-circle}** Yes | Used to list and show tags in the UI. Used to list tags in the background for [cleanup policies](../../../user/packages/container_registry/#cleanup-policy) and [Geo replication](../../../administration/geo/replication/docker_registry.md). |
+| [Check if manifest exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-manifests) | **{check-circle}** Yes | **{dotted-circle}** No | Used to get the digest of a manifest by tag. This is then used to pull the manifest and show the tag details in the UI. |
+| [Pull manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-an-image-manifest) | **{check-circle}** Yes | **{dotted-circle}** No | Used to show the image size and the manifest digest in the tag details UI. |
+| [Pull blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-a-layer) | **{check-circle}** Yes | **{dotted-circle}** No | Used to show the configuration digest and the creation date in the tag details UI. |
+| [Delete tag](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-a-tag) | **{check-circle}** Yes | **{check-circle}** Yes | Used to delete a tag from the UI and in background (cleanup policies). |
A valid authentication token is generated in GitLab Rails and embedded in all these requests before sending them to the registry.
@@ -211,22 +211,22 @@ This is a list of all the registry HTTP API operations and how they depend on th
| Operation | Method | Path | Requires Database | Requires Storage | Used by GitLab Rails * |
|--------------------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------|-------------------|------------------|------------------------|
-| [Check API version](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#api-version-check) | `GET` | `/v2/` | ✖ | ✖ | ✔ |
-| [List repositories](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-repositories) | `GET` | `/v2/_catalog` | ✔ | ✖ | ✖ |
-| [List repository tags](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-image-tags) | `GET` | `/v2/<name>/tags/list` | ✔ | ✖ | ✔ |
-| [Delete tag](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-a-tag) | `DELETE` | `/v2/<name>/tags/reference/<reference>` | ✔ | ✖ | ✔ |
-| [Check if manifest exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-manifests) | `HEAD` | `/v2/<name>/manifests/<reference>` | ✔ | ✖ | ✔ |
-| [Pull manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-an-image-manifest) | `GET` | `/v2/<name>/manifests/<reference>` | ✔ | ✖ | ✔ |
-| [Push manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pushing-an-image-manifest) | `PUT` | `/v2/<name>/manifests/<reference>` | ✔ | ✖ | ✖ |
-| [Delete manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-an-image) | `DELETE` | `/v2/<name>/manifests/<reference>` | ✔ | ✖ | ✖ |
-| [Check if blob exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-layers) | `HEAD` | `/v2/<name>/blobs/<digest>` | ✔ | ✖ | ✖ |
-| [Pull blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#fetch-blob) | `GET` | `/v2/<name>/blobs/<digest>` | ✔ | ✔ | ✔ |
-| [Delete blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#delete-blob) | `DELETE` | `/v2/<name>/blobs/<digest>` | ✔ | ✖ | ✖ |
-| [Start blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#starting-an-upload) | `POST` | `/v2/<name>/blobs/uploads/` | ✔ | ✔ | ✖ |
-| [Check blob upload status](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#get-blob-upload) | `GET` | `/v2/<name>/blobs/uploads/<uuid>` | ✔ | ✔ | ✖ |
-| [Push blob chunk](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#chunked-upload-1) | `PATCH` | `/v2/<name>/blobs/uploads/<uuid>` | ✔ | ✔ | ✖ |
-| [Complete blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#put-blob-upload) | `PUT` | `/v2/<name>/blobs/uploads/<uuid>` | ✔ | ✔ | ✖ |
-| [Cancel blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#canceling-an-upload) | `DELETE` | `/v2/<name>/blobs/uploads/<uuid>` | ✔ | ✔ | ✖ |
+| [Check API version](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#api-version-check) | `GET` | `/v2/` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes |
+| [List repositories](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-repositories) | `GET` | `/v2/_catalog` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| [List repository tags](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#listing-image-tags) | `GET` | `/v2/<name>/tags/list` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes |
+| [Delete tag](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-a-tag) | `DELETE` | `/v2/<name>/tags/reference/<reference>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes |
+| [Check if manifest exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-manifests) | `HEAD` | `/v2/<name>/manifests/<reference>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes |
+| [Pull manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pulling-an-image-manifest) | `GET` | `/v2/<name>/manifests/<reference>` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes |
+| [Push manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#pushing-an-image-manifest) | `PUT` | `/v2/<name>/manifests/<reference>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| [Delete manifest](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#deleting-an-image) | `DELETE` | `/v2/<name>/manifests/<reference>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| [Check if blob exists](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#existing-layers) | `HEAD` | `/v2/<name>/blobs/<digest>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| [Pull blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#fetch-blob) | `GET` | `/v2/<name>/blobs/<digest>` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
+| [Delete blob](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#delete-blob) | `DELETE` | `/v2/<name>/blobs/<digest>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| [Start blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#starting-an-upload) | `POST` | `/v2/<name>/blobs/uploads/` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
+| [Check blob upload status](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#get-blob-upload) | `GET` | `/v2/<name>/blobs/uploads/<uuid>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
+| [Push blob chunk](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#chunked-upload-1) | `PATCH` | `/v2/<name>/blobs/uploads/<uuid>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
+| [Complete blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#put-blob-upload) | `PUT` | `/v2/<name>/blobs/uploads/<uuid>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
+| [Cancel blob upload](https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/api.md#canceling-an-upload) | `DELETE` | `/v2/<name>/blobs/uploads/<uuid>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
`*` Please refer to the [list of interactions between registry and Rails](#from-gitlab-rails-to-registry) to know why and how.
diff --git a/doc/ci/index.md b/doc/ci/index.md
index 8d5c5c082ba..87b259af62a 100644
--- a/doc/ci/index.md
+++ b/doc/ci/index.md
@@ -38,7 +38,7 @@ read the [Introduction to CI/CD with GitLab](introduction/index.md).
<iframe src="https://www.youtube.com/embed/1iXFbchozdY" frameborder="0" allowfullscreen="true"> </iframe>
</figure>
-## Concepts
+## GitLab CI/CD concepts
GitLab CI/CD uses a number of concepts to describe and run your build and deploy.
@@ -53,7 +53,7 @@ GitLab CI/CD uses a number of concepts to describe and run your build and deploy
| [Pipeline efficiency](pipelines/pipeline_efficiency.md) | Configure your pipelines to run quickly and efficiently. |
| [Test cases](test_cases/index.md) | Create testing scenarios. |
-## Configuration
+## GitLab CI/CD configuration
GitLab CI/CD supports numerous configuration options:
@@ -72,7 +72,7 @@ GitLab CI/CD supports numerous configuration options:
Certain operations can only be performed according to the
[user](../user/permissions.md#gitlab-cicd-permissions) and [job](../user/permissions.md#job-permissions) permissions.
-## Features
+## GitLab CI/CD features
GitLab CI/CD features, grouped by DevOps stage, include:
@@ -109,7 +109,7 @@ GitLab CI/CD features, grouped by DevOps stage, include:
| [License Compliance](../user/compliance/license_compliance/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
| [Security Test reports](../user/application_security/index.md) **(ULTIMATE)** | Check for app vulnerabilities. |
-## Examples
+## GitLab CI/CD examples
See the [CI/CD examples](examples/README.md) page for example project code and tutorials for
using GitLab CI/CD with various:
@@ -118,7 +118,7 @@ using GitLab CI/CD with various:
- Languages
- Platforms
-## Administration
+## GitLab CI/CD Administration
You can change the default behavior of GitLab CI/CD for:
diff --git a/doc/ci/migration/jenkins.md b/doc/ci/migration/jenkins.md
index aed1edf09e6..99bdc255f0e 100644
--- a/doc/ci/migration/jenkins.md
+++ b/doc/ci/migration/jenkins.md
@@ -199,7 +199,7 @@ GitLab takes advantage of our connected ecosystem to automatically pull these ki
your Merge Requests, pipeline details pages, and other locations. You may find that you actually don't
need to configure anything to have these appear.
-If they aren't working as expected, or if you'd like to see what's available, our [CI feature index](../index.md#features) has the full list
+If they aren't working as expected, or if you'd like to see what's available, our [CI feature index](../index.md#gitlab-cicd-features) has the full list
of bundled features and links to the documentation for each.
### Templates
diff --git a/doc/development/application_limits.md b/doc/development/application_limits.md
index b606cda1124..2075e7cda3c 100644
--- a/doc/development/application_limits.md
+++ b/doc/development/application_limits.md
@@ -40,9 +40,7 @@ It's recommended to create two separate migration script files.
desired limit using `create_or_update_plan_limit` migration helper, such as:
```ruby
- class InsertProjectHooksPlanLimits < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
+ class InsertProjectHooksPlanLimits < Gitlab::Database::Migration[1.0]
def up
create_or_update_plan_limit('project_hooks', 'default', 0)
create_or_update_plan_limit('project_hooks', 'free', 10)
diff --git a/doc/development/avoiding_downtime_in_migrations.md b/doc/development/avoiding_downtime_in_migrations.md
index b844415c94e..9418eafa487 100644
--- a/doc/development/avoiding_downtime_in_migrations.md
+++ b/doc/development/avoiding_downtime_in_migrations.md
@@ -95,9 +95,7 @@ renaming. For example
```ruby
# A regular migration in db/migrate
-class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class RenameUsersUpdatedAtToUpdatedAtTimestamp < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -125,9 +123,7 @@ We can perform this cleanup using
```ruby
# A post-deployment migration in db/post_migrate
-class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class CleanupUsersUpdatedAtRename < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -174,9 +170,7 @@ as follows:
```ruby
# A regular migration in db/migrate
-class ChangeUsersUsernameStringToText < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class ChangeUsersUsernameStringToText < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -195,9 +189,7 @@ Next we need to clean up our changes using a post-deployment migration:
```ruby
# A post-deployment migration in db/post_migrate
-class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class ChangeUsersUsernameStringToTextCleanup < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -245,9 +237,7 @@ the work / load over a longer time period, without slowing down deployments.
For example, to change the column type using a background migration:
```ruby
-class ExampleMigration < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class ExampleMigration < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
class Issue < ActiveRecord::Base
@@ -289,9 +279,7 @@ release) by a cleanup migration, which should steal from the queue and handle
any remaining rows. For example:
```ruby
-class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class MigrateRemainingIssuesClosedAt < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
class Issue < ActiveRecord::Base
diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md
index 695c565ca83..123583dc9db 100644
--- a/doc/development/background_migrations.md
+++ b/doc/development/background_migrations.md
@@ -254,7 +254,7 @@ existing data. Since we're dealing with a lot of rows we'll schedule jobs in
batches instead of doing this one by one:
```ruby
-class ScheduleExtractServicesUrl < ActiveRecord::Migration[4.2]
+class ScheduleExtractServicesUrl < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -281,7 +281,7 @@ jobs and manually run on any un-migrated rows. Such a migration would look like
this:
```ruby
-class ConsumeRemainingExtractServicesUrlJobs < ActiveRecord::Migration[4.2]
+class ConsumeRemainingExtractServicesUrlJobs < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -463,8 +463,6 @@ end
```ruby
# Post deployment migration
-include Gitlab::Database::MigrationHelpers
-
MIGRATION = 'YourBackgroundMigrationName'
DELAY_INTERVAL = 2.minutes.to_i # can be different
BATCH_SIZE = 10_000 # can be different
@@ -494,8 +492,6 @@ You can reschedule pending migrations from the `background_migration_jobs` table
```ruby
# Post deployment migration
-include Gitlab::Database::MigrationHelpers
-
MIGRATION = 'YourBackgroundMigrationName'
DELAY_INTERVAL = 2.minutes
diff --git a/doc/development/cascading_settings.md b/doc/development/cascading_settings.md
index d1c5756fa2c..f5562206654 100644
--- a/doc/development/cascading_settings.md
+++ b/doc/development/cascading_settings.md
@@ -38,7 +38,7 @@ Settings are not cascading by default. To define a cascading setting, take the f
`application_settings`.
```ruby
- class AddDelayedProjectRemovalCascadingSetting < ActiveRecord::Migration[6.0]
+ class AddDelayedProjectRemovalCascadingSetting < Gitlab::Database::Migration[1.0]
include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings
def up
diff --git a/doc/development/database/add_foreign_key_to_existing_column.md b/doc/development/database/add_foreign_key_to_existing_column.md
index f83dc35b4a6..0e1dd0f390e 100644
--- a/doc/development/database/add_foreign_key_to_existing_column.md
+++ b/doc/development/database/add_foreign_key_to_existing_column.md
@@ -66,9 +66,7 @@ In the example above, you'd be still able to update records in the `emails` tabl
Migration file for adding `NOT VALID` foreign key:
```ruby
-class AddNotValidForeignKeyToEmailsUser < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
+class AddNotValidForeignKeyToEmailsUser < Gitlab::Database::Migration[1.0]
def up
# safe to use: it requires short lock on the table since we don't validate the foreign key
add_foreign_key :emails, :users, on_delete: :cascade, validate: false
@@ -92,9 +90,7 @@ In case the data volume is higher (>1000 records), it's better to create a backg
Example for cleaning up records in the `emails` table within a database migration:
```ruby
-class RemoveRecordsWithoutUserFromEmailsTable < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
+class RemoveRecordsWithoutUserFromEmailsTable < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
class Email < ActiveRecord::Base
@@ -126,9 +122,7 @@ Migration file for validating the foreign key:
```ruby
# frozen_string_literal: true
-class ValidateForeignKeyOnEmailUsers < ActiveRecord::Migration[5.2]
- include Gitlab::Database::MigrationHelpers
-
+class ValidateForeignKeyOnEmailUsers < Gitlab::Database::Migration[1.0]
def up
validate_foreign_key :emails, :user_id
end
diff --git a/doc/development/database/not_null_constraints.md b/doc/development/database/not_null_constraints.md
index 178a207dab5..de070f7e434 100644
--- a/doc/development/database/not_null_constraints.md
+++ b/doc/development/database/not_null_constraints.md
@@ -25,7 +25,7 @@ For example, consider a migration that creates a table with two `NOT NULL` colum
`db/migrate/20200401000001_create_db_guides.rb`:
```ruby
-class CreateDbGuides < ActiveRecord::Migration[6.0]
+class CreateDbGuides < Gitlab::Database::Migration[1.0]
def change
create_table :db_guides do |t|
t.bigint :stars, default: 0, null: false
@@ -44,7 +44,7 @@ For example, consider a migration that adds a new `NOT NULL` column `active` to
`db/migrate/20200501000001_add_active_to_db_guides.rb`:
```ruby
-class AddExtendedTitleToSprints < ActiveRecord::Migration[6.0]
+class AddExtendedTitleToSprints < Gitlab::Database::Migration[1.0]
def change
add_column :db_guides, :active, :boolean, default: true, null: false
end
@@ -111,9 +111,7 @@ with `validate: false` in a post-deployment migration,
`db/post_migrate/20200501000001_add_not_null_constraint_to_epics_description.rb`:
```ruby
-class AddNotNullConstraintToEpicsDescription < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class AddNotNullConstraintToEpicsDescription < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -144,9 +142,7 @@ so we are going to add a post-deployment migration for the 13.0 milestone (curre
`db/post_migrate/20200501000002_cleanup_epics_with_null_description.rb`:
```ruby
-class CleanupEpicsWithNullDescription < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class CleanupEpicsWithNullDescription < Gitlab::Database::Migration[1.0]
# With BATCH_SIZE=1000 and epics.count=29500 on GitLab.com
# - 30 iterations will be run
# - each requires on average ~150ms
@@ -184,9 +180,7 @@ migration helper in a final post-deployment migration,
`db/post_migrate/20200601000001_validate_not_null_constraint_on_epics_description.rb`:
```ruby
-class ValidateNotNullConstraintOnEpicsDescription < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class ValidateNotNullConstraintOnEpicsDescription < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
diff --git a/doc/development/database/rename_database_tables.md b/doc/development/database/rename_database_tables.md
index 8ac50d2c0a0..29c17e1a235 100644
--- a/doc/development/database/rename_database_tables.md
+++ b/doc/development/database/rename_database_tables.md
@@ -60,8 +60,6 @@ Consider the next release as "Release N.M".
Execute a standard migration (not a post-migration):
```ruby
- include Gitlab::Database::MigrationHelpers
-
def up
rename_table_safely(:issues, :tickets)
end
@@ -96,8 +94,6 @@ At this point, we don't have applications using the old database table name in t
1. Remove the database view through a post-migration:
```ruby
- include Gitlab::Database::MigrationHelpers
-
def up
finalize_table_rename(:issues, :tickets)
end
diff --git a/doc/development/database/strings_and_the_text_data_type.md b/doc/development/database/strings_and_the_text_data_type.md
index 688d811b897..92d70c9cba5 100644
--- a/doc/development/database/strings_and_the_text_data_type.md
+++ b/doc/development/database/strings_and_the_text_data_type.md
@@ -47,9 +47,7 @@ For example, consider a migration that creates a table with two text columns,
`db/migrate/20200401000001_create_db_guides.rb`:
```ruby
-class CreateDbGuides < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class CreateDbGuides < Gitlab::Database::Migration[1.0]
def up
create_table_with_constraints :db_guides do |t|
t.bigint :stars, default: 0, null: false
@@ -84,7 +82,7 @@ For example, consider a migration that adds a new text column `extended_title` t
`db/migrate/20200501000001_add_extended_title_to_sprints.rb`:
```ruby
-class AddExtendedTitleToSprints < ActiveRecord::Migration[6.0]
+class AddExtendedTitleToSprints < Gitlab::Database::Migration[1.0]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20200501000002_add_text_limit_to_sprints_extended_title
@@ -99,8 +97,7 @@ A second migration should follow the first one with a limit added to `extended_t
`db/migrate/20200501000002_add_text_limit_to_sprints_extended_title.rb`:
```ruby
-class AddTextLimitToSprintsExtendedTitle < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
+class AddTextLimitToSprintsExtendedTitle < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -175,9 +172,7 @@ in a post-deployment migration,
`db/post_migrate/20200501000001_add_text_limit_migration.rb`:
```ruby
-class AddTextLimitMigration < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class AddTextLimitMigration < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
@@ -208,9 +203,7 @@ to add a background migration for the 13.0 milestone (current),
`db/post_migrate/20200501000002_schedule_cap_title_length_on_issues.rb`:
```ruby
-class ScheduleCapTitleLengthOnIssues < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class ScheduleCapTitleLengthOnIssues < Gitlab::Database::Migration[1.0]
# Info on how many records will be affected on GitLab.com
# time each batch needs to run on average, etc ...
BATCH_SIZE = 5000
@@ -255,9 +248,7 @@ helper in a final post-deployment migration,
`db/post_migrate/20200601000001_validate_text_limit_migration.rb`:
```ruby
-class ValidateTextLimitMigration < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
-
+class ValidateTextLimitMigration < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
diff --git a/doc/development/database/table_partitioning.md b/doc/development/database/table_partitioning.md
index 207d5a733ce..5319c73aad0 100644
--- a/doc/development/database/table_partitioning.md
+++ b/doc/development/database/table_partitioning.md
@@ -173,7 +173,7 @@ An example migration of partitioning the `audit_events` table by its
`created_at` column would look like:
```ruby
-class PartitionAuditEvents < ActiveRecord::Migration[6.0]
+class PartitionAuditEvents < Gitlab::Database::Migration[1.0]
include Gitlab::Database::PartitioningMigrationHelpers
def up
@@ -200,7 +200,7 @@ into the partitioned copy.
Continuing the above example, the migration would look like:
```ruby
-class BackfillPartitionAuditEvents < ActiveRecord::Migration[6.0]
+class BackfillPartitionAuditEvents < Gitlab::Database::Migration[1.0]
include Gitlab::Database::PartitioningMigrationHelpers
def up
@@ -233,7 +233,7 @@ failed jobs.
Once again, continuing the example, this migration would look like:
```ruby
-class CleanupPartitionedAuditEventsBackfill < ActiveRecord::Migration[6.0]
+class CleanupPartitionedAuditEventsBackfill < Gitlab::Database::Migration[1.0]
include Gitlab::Database::PartitioningMigrationHelpers
def up
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 53825f0904a..517a59a0ff3 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -171,6 +171,45 @@ If you need to translate strings in the Vue component's JavaScript, you can impo
To test Vue translations, learn about [manually testing translations from the UI](#manually-test-translations-from-the-ui).
+### Test files
+
+Test expectations against externalized contents should not be hard coded,
+because we may need to run the tests with non-default locale, and tests with
+hard coded contents will fail.
+
+This means any expectations against externalized contents should call the
+same externalizing method to match the translation.
+
+Bad:
+
+```ruby
+click_button 'Submit review'
+
+expect(rendered).to have_content('Thank you for your feedback!')
+```
+
+Good:
+
+```ruby
+click_button _('Submit review')
+
+expect(rendered).to have_content(_('Thank you for your feedback!'))
+```
+
+This includes JavaScript tests:
+
+Bad:
+
+```javascript
+expect(findUpdateIgnoreStatusButton().text()).toBe('Ignore');
+```
+
+Good:
+
+```javascript
+expect(findUpdateIgnoreStatusButton().text()).toBe(__('Ignore'));
+```
+
#### Recommendations
If strings are reused throughout a component, it can be useful to define these strings as variables. We recommend defining an `i18n` property on the component's `$options` object. If there is a mixture of many-use and single-use strings in the component, consider using this approach to create a local [Single Source of Truth](https://about.gitlab.com/handbook/values/#single-source-of-truth) for externalized strings.
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index bbaa6527e84..f6f7cfcdd6c 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -70,7 +70,7 @@ graph LR
H -->|Yes| E[Regular migration]
H -->|No| I[Post-deploy migration<br/>+ feature flag]
-
+
D -->|Yes| F[Post-deploy migration]
D -->|No| G[Background migration]
```
@@ -217,6 +217,39 @@ In case you need to insert, update, or delete a significant amount of data, you:
- Must disable the single transaction with `disable_ddl_transaction!`.
- Should consider doing it in a [Background Migration](background_migrations.md).
+## Migration helpers and versioning
+
+Various helper methods are available for many common patterns in database migrations. Those
+helpers can be found in `Gitlab::Database::MigrationHelpers` and related modules.
+
+In order to allow changing a helper's behavior over time, we implement a versioning scheme
+for migration helpers. This allows us to maintain the behavior of a helper for already
+existing migrations but change the behavior for any new migrations.
+
+For that purpose, all database migrations should inherit from `Gitlab::Database::Migration`,
+which is a "versioned" class. For new migrations, the latest version should be used (which
+can be looked up in `Gitlab::Database::Migration::MIGRATION_CLASSES`) to use the latest version
+of migration helpers.
+
+In this example, we use version 1.0 of the migration class:
+
+```ruby
+class TestMigration < Gitlab::Database::Migration[1.0]
+ def change
+ end
+end
+```
+
+NOTE:
+It is discouraged to include `Gitlab::Database::MigrationHelpers` directly into a
+migration. Instead, the latest version of `Gitlab::Database::Migration` will expose the latest
+version of migration helpers automatically.
+
+NOTE:
+Migration helpers and versioning are available starting from [14.3](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68986).
+For merge requests targeting previous stable branches, the old format needs to be used and we continue
+to inherit from `ActiveRecord::Migration[6.1]` instead of `Gitlab::Database::Migration[1.0]` for those.
+
## Retry mechanism when acquiring database locks
When changing the database schema, we use helper methods to invoke DDL (Data Definition
@@ -256,8 +289,6 @@ lock allow the database to process other statements.
**Removing a column:**
```ruby
-include Gitlab::Database::MigrationHelpers
-
def up
with_lock_retries do
remove_column :users, :full_name
@@ -278,8 +309,6 @@ you should do as much as possible inside the transaction rather than trying to g
Be careful about running long database statements within the block. The acquired locks are kept until the transaction (block) finishes and depending on the lock type, it might block other database operations.
```ruby
-include Gitlab::Database::MigrationHelpers
-
def up
with_lock_retries do
add_column :users, :full_name, :string
@@ -298,8 +327,6 @@ end
**Removing a foreign key:**
```ruby
-include Gitlab::Database::MigrationHelpers
-
def up
with_lock_retries do
remove_foreign_key :issues, :projects
@@ -316,8 +343,6 @@ end
**Changing default value for a column:**
```ruby
-include Gitlab::Database::MigrationHelpers
-
def up
with_lock_retries do
change_column_default :merge_requests, :lock_version, from: nil, to: 0
@@ -387,8 +412,6 @@ We can use the `add_concurrent_foreign_key` method in this case, as this helper
has the lock retries built into it.
```ruby
-include Gitlab::Database::MigrationHelpers
-
disable_ddl_transaction!
def up
@@ -405,8 +428,6 @@ end
Adding foreign key to `users`:
```ruby
-include Gitlab::Database::MigrationHelpers
-
disable_ddl_transaction!
def up
@@ -498,11 +519,11 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
+class MyMigration < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
INDEX_NAME = 'index_name'
+
def up
remove_concurrent_index :table_name, :column_name, name: INDEX_NAME
end
@@ -549,7 +570,7 @@ by calling the method `disable_ddl_transaction!` in the body of your migration
class like so:
```ruby
-class MyMigration < ActiveRecord::Migration[6.0]
+class MyMigration < Gitlab::Database::Migration[1.0]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
@@ -594,7 +615,7 @@ The easiest way to test for existence of an index by name is to use the
be used with a name option. For example:
```ruby
-class MyMigration < ActiveRecord::Migration[6.0]
+class MyMigration < Gitlab::Database::Migration[1.0]
include Gitlab::Database::MigrationHelpers
INDEX_NAME = 'index_name'
@@ -631,7 +652,7 @@ Here's an example where we add a new column with a foreign key
constraint. Note it includes `index: true` to create an index for it.
```ruby
-class Migration < ActiveRecord::Migration[6.0]
+class Migration < Gitlab::Database::Migration[1.0]
def change
add_reference :model, :other_model, index: true, foreign_key: { on_delete: :cascade }
@@ -677,7 +698,7 @@ expensive and disruptive operation for larger tables, but in reality it's not.
Take the following migration as an example:
```ruby
-class DefaultRequestAccessGroups < ActiveRecord::Migration[5.2]
+class DefaultRequestAccessGroups < Gitlab::Database::Migration[1.0]
def change
change_column_default(:namespaces, :request_access_enabled, from: false, to: true)
end
@@ -884,7 +905,7 @@ The Rails 5 natively supports `JSONB` (binary JSON) column type.
Example migration adding this column:
```ruby
-class AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0]
+class AddOptionsToBuildMetadata < Gitlab::Database::Migration[1.0]
def change
add_column :ci_builds_metadata, :config_options, :jsonb
end
@@ -916,7 +937,7 @@ Do not store `attr_encrypted` attributes as `:text` in the database; use
efficient:
```ruby
-class AddSecretToSomething < ActiveRecord::Migration[5.0]
+class AddSecretToSomething < Gitlab::Database::Migration[1.0]
def change
add_column :something, :encrypted_secret, :binary
add_column :something, :encrypted_secret_iv, :binary
@@ -974,7 +995,7 @@ If you need more complex logic, you can define and use models local to a
migration. For example:
```ruby
-class MyMigration < ActiveRecord::Migration[6.0]
+class MyMigration < Gitlab::Database::Migration[1.0]
class Project < ActiveRecord::Base
self.table_name = 'projects'
end
@@ -1073,7 +1094,7 @@ in a previous migration.
It is important not to leave out the `User.reset_column_information` command, in order to ensure that the old schema is dropped from the cache and ActiveRecord loads the updated schema information.
```ruby
-class AddAndSeedMyColumn < ActiveRecord::Migration[6.0]
+class AddAndSeedMyColumn < Gitlab::Database::Migration[1.0]
class User < ActiveRecord::Base
self.table_name = 'users'
end
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 66e980978bf..da6ba14cdd8 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -36,9 +36,7 @@ After you add or change an existing common metric, you must [re-run the import s
Or, you can create a database migration:
```ruby
-class ImportCommonMetrics < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
+class ImportCommonMetrics < Gitlab::Database::Migration[1.0]
def up
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index 8373616ba3f..04b7e2f5c45 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -1002,9 +1002,7 @@ When renaming queues, use the `sidekiq_queue_migrate` helper migration method
in a **post-deployment migration**:
```ruby
-class MigrateTheRenamedSidekiqQueue < ActiveRecord::Migration[5.0]
- include Gitlab::Database::MigrationHelpers
-
+class MigrateTheRenamedSidekiqQueue < Gitlab::Database::Migration[1.0]
def up
sidekiq_queue_migrate 'old_queue_name', to: 'new_queue_name'
end
diff --git a/doc/development/single_table_inheritance.md b/doc/development/single_table_inheritance.md
index aa4fe540b0d..eb406b02a91 100644
--- a/doc/development/single_table_inheritance.md
+++ b/doc/development/single_table_inheritance.md
@@ -31,7 +31,7 @@ could result in loading unexpected code or associations which may cause unintend
side effects or failures during upgrades.
```ruby
-class SomeMigration < ActiveRecord::Migration[6.0]
+class SomeMigration < Gitlab::Database::Migration[1.0]
class Services < ActiveRecord::Base
self.table_name = 'services'
self.inheritance_column = :_type_disabled
@@ -47,7 +47,7 @@ This ensures that the migration loads the columns for the migration in isolation
and the helper disables STI by default.
```ruby
-class EnqueueSomeBackgroundMigration < ActiveRecord::Migration[6.0]
+class EnqueueSomeBackgroundMigration < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
diff --git a/doc/development/sql.md b/doc/development/sql.md
index 40ee19c0b9e..3483305c113 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -102,7 +102,7 @@ transaction. Transactions for migrations can be disabled using the following
pattern:
```ruby
-class MigrationName < ActiveRecord::Migration[4.2]
+class MigrationName < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
end
```
@@ -110,7 +110,7 @@ end
For example:
```ruby
-class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration[4.2]
+class AddUsersLowerUsernameEmailIndexes < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
def up
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index abb55857e1f..937a630f406 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -367,26 +367,34 @@ If needed, you can scope interactions within a specific area of the page by usin
As you will likely be scoping to an element such as a `div`, which typically does not have a label,
you may use a `data-testid` selector in this case.
+##### Externalized contents
+
+Test expectations against externalized contents should call the same
+externalizing method to match the translation. For example, you should use the `_`
+method in Ruby and `__` method in JavaScript.
+
+See [Internationalization for GitLab - Test files](../i18n/externalization.md#test-files) for details.
+
##### Actions
Where possible, use more specific [actions](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Node/Actions), such as the ones below.
```ruby
# good
-click_button 'Submit review'
+click_button _('Submit review')
-click_link 'UI testing docs'
+click_link _('UI testing docs')
-fill_in 'Search projects', with: 'gitlab' # fill in text input with text
+fill_in _('Search projects'), with: 'gitlab' # fill in text input with text
-select 'Last updated', from: 'Sort by' # select an option from a select input
+select _('Last updated'), from: 'Sort by' # select an option from a select input
-check 'Checkbox label'
-uncheck 'Checkbox label'
+check _('Checkbox label')
+uncheck _('Checkbox label')
-choose 'Radio input label'
+choose _('Radio input label')
-attach_file('Attach a file', '/path/to/file.png')
+attach_file(_('Attach a file'), '/path/to/file.png')
# bad - interactive elements must have accessible names, so
# we should be able to use one of the specific actions above
@@ -403,17 +411,17 @@ Where possible, use more specific [finders](https://rubydoc.info/github/teamcapy
```ruby
# good
-find_button 'Submit review'
-find_button 'Submit review', disabled: true
+find_button _('Submit review')
+find_button _('Submit review'), disabled: true
-find_link 'UI testing docs'
-find_link 'UI testing docs', href: docs_url
+find_link _('UI testing docs')
+find_link _('UI testing docs'), href: docs_url
-find_field 'Search projects'
-find_field 'Search projects', with: 'gitlab' # find the input field with text
-find_field 'Search projects', disabled: true
-find_field 'Checkbox label', checked: true
-find_field 'Checkbox label', unchecked: true
+find_field _('Search projects')
+find_field _('Search projects'), with: 'gitlab' # find the input field with text
+find_field _('Search projects'), disabled: true
+find_field _('Checkbox label'), checked: true
+find_field _('Checkbox label'), unchecked: true
# acceptable when finding a element that is not a button, link, or field
find('[data-testid="element"]')
@@ -425,31 +433,31 @@ Where possible, use more specific [matchers](https://rubydoc.info/github/teamcap
```ruby
# good
-expect(page).to have_button 'Submit review'
-expect(page).to have_button 'Submit review', disabled: true
-expect(page).to have_button 'Notifications', class: 'is-checked' # assert the "Notifications" GlToggle is checked
+expect(page).to have_button _('Submit review')
+expect(page).to have_button _('Submit review'), disabled: true
+expect(page).to have_button _('Notifications'), class: 'is-checked' # assert the "Notifications" GlToggle is checked
-expect(page).to have_link 'UI testing docs'
-expect(page).to have_link 'UI testing docs', href: docs_url # assert the link has an href
+expect(page).to have_link _('UI testing docs')
+expect(page).to have_link _('UI testing docs'), href: docs_url # assert the link has an href
-expect(page).to have_field 'Search projects'
-expect(page).to have_field 'Search projects', disabled: true
-expect(page).to have_field 'Search projects', with: 'gitlab' # assert the input field has text
+expect(page).to have_field _('Search projects')
+expect(page).to have_field _('Search projects'), disabled: true
+expect(page).to have_field _('Search projects'), with: 'gitlab' # assert the input field has text
-expect(page).to have_checked_field 'Checkbox label'
-expect(page).to have_unchecked_field 'Radio input label'
+expect(page).to have_checked_field _('Checkbox label')
+expect(page).to have_unchecked_field _('Radio input label')
-expect(page).to have_select 'Sort by'
-expect(page).to have_select 'Sort by', selected: 'Last updated' # assert the option is selected
-expect(page).to have_select 'Sort by', options: ['Last updated', 'Created date', 'Due date'] # assert an exact list of options
-expect(page).to have_select 'Sort by', with_options: ['Created date', 'Due date'] # assert a partial list of options
+expect(page).to have_select _('Sort by')
+expect(page).to have_select _('Sort by'), selected: 'Last updated' # assert the option is selected
+expect(page).to have_select _('Sort by'), options: ['Last updated', 'Created date', 'Due date'] # assert an exact list of options
+expect(page).to have_select _('Sort by'), with_options: ['Created date', 'Due date'] # assert a partial list of options
-expect(page).to have_text 'Some paragraph text.'
-expect(page).to have_text 'Some paragraph text.', exact: true # assert exact match
+expect(page).to have_text _('Some paragraph text.')
+expect(page).to have_text _('Some paragraph text.'), exact: true # assert exact match
expect(page).to have_current_path 'gitlab/gitlab-test/-/issues'
-expect(page).to have_title 'Not Found'
+expect(page).to have_title _('Not Found')
# acceptable when a more specific matcher above is not possible
expect(page).to have_css 'h2', text: 'Issue title'
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index e698341b4b5..6045dece0c2 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -34,6 +34,7 @@ These are rate limits you can set in the Admin Area of your instance:
- [Raw endpoints rate limits](../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
- [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md)
- [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)
## Non-configurable limits
diff --git a/doc/user/admin_area/settings/git_lfs_rate_limits.md b/doc/user/admin_area/settings/git_lfs_rate_limits.md
new file mode 100644
index 00000000000..8a0754374e2
--- /dev/null
+++ b/doc/user/admin_area/settings/git_lfs_rate_limits.md
@@ -0,0 +1,35 @@
+---
+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
+---
+
+# Git LFS Rate Limits **(FREE SELF)**
+
+[Git LFS (Large File Storage)](../../../topics/git/lfs/index.md) is a Git extension
+for handling large files. If you use Git LFS in your repository, common Git operations
+can generate many Git LFS requests. You can enforce
+[general user and IP rate limits](user_and_ip_rate_limits.md), but you can also
+override the general setting to enforce additional limits on Git LFS requests. This
+override can improve the security and durability of your web application. Aside from
+precedence, this configuration provides the same features as the general user and IP
+rate limits.
+
+## Configure Git LFS rate limits
+
+Git LFS rate limits are disabled by default. If enabled and configured, these limits
+supersede the [general user and IP rate limits](user_and_ip_rate_limits.md):
+
+1. On the top bar, select **Menu >** **{admin}** **Admin**.
+1. On the left sidebar, select **Settings > Network**.
+1. Expand **Git LFS Rate Limits**.
+1. Select **Enable authenticated Git LFS request rate limit**.
+1. Enter a value for **Max authenticated Git LFS requests per period per user**.
+1. Enter a value for **Authenticated Git LFS rate limit period in seconds**.
+1. Select **Save changes**.
+
+## Resources
+
+- [Rate limiting](../../../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 42e75194c5a..d8bc6d21457 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -96,6 +96,7 @@ To access the default page for Admin Area settings:
| Performance optimization | [Write to "authorized_keys" file](../../../administration/operations/fast_ssh_key_lookup.md#setting-up-fast-lookup-via-gitlab-shell) and [Push event activities limit and bulk push events](push_event_activities_limit.md). Various settings that affect GitLab performance. |
| [User and IP rate limits](user_and_ip_rate_limits.md) | Configure limits for web and API requests. |
| [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. |
| [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 a756b35c680..7e547541079 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
@@ -136,6 +136,7 @@ The possible names are:
- `throttle_authenticated_protected_paths_web`
- `throttle_unauthenticated_packages_api`
- `throttle_authenticated_packages_api`
+- `throttle_authenticated_git_lfs`
For example, to try out throttles for all authenticated requests to
non-protected paths can be done by setting
diff --git a/generator_templates/active_record/migration/migration.rb b/generator_templates/active_record/migration/migration.rb
index dddd6e247d4..103c2fd7ea2 100644
--- a/generator_templates/active_record/migration/migration.rb
+++ b/generator_templates/active_record/migration/migration.rb
@@ -3,10 +3,7 @@
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
-class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
- # Uncomment the following include if you require helper functions:
- # include Gitlab::Database::MigrationHelpers
-
+class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Database::Migration.current_version %>]
# When using the methods "add_concurrent_index" or "remove_concurrent_index"
# you must disable the use of transactions
# as these methods can not run in an existing transaction.
diff --git a/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb b/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
index 689d1de9d17..c456fa29ea8 100644
--- a/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
+++ b/generator_templates/post_deployment_migration/post_deployment_migration/migration.rb
@@ -3,10 +3,7 @@
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
-class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
- # Uncomment the following include if you require helper functions:
- # include Gitlab::Database::MigrationHelpers
-
+class <%= migration_class_name %> < Gitlab::Database::Migration[<%= Gitlab::Database::Migration.current_version %>]
# When using the methods "add_concurrent_index" or "remove_concurrent_index"
# you must disable the use of transactions
# as these methods can not run in an existing transaction.
@@ -20,6 +17,9 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
# comments:
# disable_ddl_transaction!
- def change
+ def up
+ end
+
+ def down
end
end
diff --git a/lib/gitlab/auth/auth_finders.rb b/lib/gitlab/auth/auth_finders.rb
index a7312ac759a..f6ee08defcf 100644
--- a/lib/gitlab/auth/auth_finders.rb
+++ b/lib/gitlab/auth/auth_finders.rb
@@ -338,6 +338,10 @@ module Gitlab
Gitlab::PathRegex.repository_git_route_regex.match?(current_request.path)
end
+ def git_lfs_request?
+ Gitlab::PathRegex.repository_git_lfs_route_regex.match?(current_request.path)
+ end
+
def archive_request?
current_request.path.include?('/-/archive/')
end
diff --git a/lib/gitlab/database/migration.rb b/lib/gitlab/database/migration.rb
new file mode 100644
index 00000000000..8990460ccf0
--- /dev/null
+++ b/lib/gitlab/database/migration.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ class Migration
+ # This implements a simple versioning scheme for migration helpers.
+ #
+ # We need to be able to version helpers, so we can change their behavior without
+ # altering the behavior of already existing migrations in incompatible ways.
+ #
+ # We can continue to change the behavior of helpers without bumping the version here,
+ # *if* the change is backwards-compatible.
+ #
+ # If not, we would typically override the helper method in a new MigrationHelpers::V[0-9]+
+ # class and create a new entry with a bumped version below.
+ #
+ # We use major version bumps to indicate significant changes and minor version bumps
+ # to indicate backwards-compatible or otherwise minor changes (e.g. a Rails version bump).
+ # However, this hasn't been strictly formalized yet.
+ MIGRATION_CLASSES = {
+ 1.0 => Class.new(ActiveRecord::Migration[6.1]) do
+ include Gitlab::Database::MigrationHelpers::V2
+ end
+ }.freeze
+
+ def self.[](version)
+ MIGRATION_CLASSES[version] || raise(ArgumentError, "Unknown migration version: #{version}")
+ end
+
+ # The current version to be used in new migrations
+ def self.current_version
+ 1.0
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/subscribers/rack_attack.rb b/lib/gitlab/metrics/subscribers/rack_attack.rb
index 1c7767f5ca9..7ee6649a3f0 100644
--- a/lib/gitlab/metrics/subscribers/rack_attack.rb
+++ b/lib/gitlab/metrics/subscribers/rack_attack.rb
@@ -20,7 +20,8 @@ module Gitlab
:throttle_authenticated_web,
:throttle_authenticated_protected_paths_api,
:throttle_authenticated_protected_paths_web,
- :throttle_authenticated_packages_api
+ :throttle_authenticated_packages_api,
+ :throttle_authenticated_git_lfs
].freeze
PAYLOAD_KEYS = [
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 4cb47ffc6d9..c648f4d1fd0 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -184,6 +184,10 @@ module Gitlab
@repository_git_route_regex ||= /#{repository_route_regex}\.git/.freeze
end
+ def repository_git_lfs_route_regex
+ @repository_git_lfs_route_regex ||= %r{#{repository_git_route_regex}\/(info\/lfs|gitlab-lfs)\/}.freeze
+ end
+
def repository_wiki_git_route_regex
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.*\.wiki\.git/.freeze
end
diff --git a/lib/gitlab/rack_attack.rb b/lib/gitlab/rack_attack.rb
index 175f32bd4c6..e913a4d583c 100644
--- a/lib/gitlab/rack_attack.rb
+++ b/lib/gitlab/rack_attack.rb
@@ -139,6 +139,12 @@ module Gitlab
end
end
+ throttle_or_track(rack_attack, 'throttle_authenticated_git_lfs', Gitlab::Throttle.throttle_authenticated_git_lfs_options) do |req|
+ if req.throttle_authenticated_git_lfs?
+ req.throttled_user_id([:api])
+ end
+ end
+
rack_attack.safelist('throttle_bypass_header') do |req|
Gitlab::Throttle.bypass_header.present? &&
req.get_header(Gitlab::Throttle.bypass_header) == '1'
diff --git a/lib/gitlab/rack_attack/request.rb b/lib/gitlab/rack_attack/request.rb
index 7fee6a1b43d..cfe5b6ca4ee 100644
--- a/lib/gitlab/rack_attack/request.rb
+++ b/lib/gitlab/rack_attack/request.rb
@@ -73,6 +73,7 @@ module Gitlab
def throttle_authenticated_web?
web_request? &&
+ !throttle_authenticated_git_lfs? &&
Gitlab::Throttle.settings.throttle_authenticated_web_enabled
end
@@ -109,6 +110,11 @@ module Gitlab
Gitlab::Throttle.settings.throttle_authenticated_packages_api_enabled
end
+ def throttle_authenticated_git_lfs?
+ git_lfs_path? &&
+ Gitlab::Throttle.settings.throttle_authenticated_git_lfs_enabled
+ end
+
private
def authenticated_user_id(request_formats)
@@ -130,6 +136,10 @@ module Gitlab
def packages_api_path?
path =~ ::Gitlab::Regex::Packages::API_PATH_REGEX
end
+
+ def git_lfs_path?
+ path =~ Gitlab::PathRegex.repository_git_lfs_route_regex
+ end
end
end
end
diff --git a/lib/gitlab/throttle.rb b/lib/gitlab/throttle.rb
index 8f045021088..df2b3fbc1af 100644
--- a/lib/gitlab/throttle.rb
+++ b/lib/gitlab/throttle.rb
@@ -63,6 +63,13 @@ module Gitlab
{ limit: limit_proc, period: period_proc }
end
+ def self.throttle_authenticated_git_lfs_options
+ limit_proc = proc { |req| settings.throttle_authenticated_git_lfs_requests_per_period }
+ period_proc = proc { |req| settings.throttle_authenticated_git_lfs_period_in_seconds.seconds }
+
+ { limit: limit_proc, period: period_proc }
+ end
+
def self.rate_limiting_response_text
(settings.rate_limiting_response_text.presence || DEFAULT_RATE_LIMITING_RESPONSE_TEXT) + "\n"
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2005b87fbe4..6fec63667af 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4769,6 +4769,12 @@ msgstr ""
msgid "Authenticated API requests"
msgstr ""
+msgid "Authenticated Git LFS rate limit period in seconds"
+msgstr ""
+
+msgid "Authenticated Git LFS request rate limit"
+msgstr ""
+
msgid "Authenticated web rate limit period in seconds"
msgstr ""
@@ -8506,6 +8512,9 @@ msgstr ""
msgid "Configure settings for Advanced Search with Elasticsearch."
msgstr ""
+msgid "Configure specific limits for Git LFS requests that supersede the general user and IP rate limits."
+msgstr ""
+
msgid "Configure specific limits for Packages API requests that supersede the general user and IP rate limits."
msgstr ""
@@ -12413,6 +12422,9 @@ msgstr ""
msgid "Enable authenticated API request rate limit"
msgstr ""
+msgid "Enable authenticated Git LFS request rate limit"
+msgstr ""
+
msgid "Enable authentication"
msgstr ""
@@ -15203,6 +15215,9 @@ msgstr ""
msgid "Git GC period"
msgstr ""
+msgid "Git LFS Rate Limits"
+msgstr ""
+
msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
@@ -16526,6 +16541,9 @@ msgstr ""
msgid "Helps reduce request volume (e.g. from crawlers or abusive bots)"
msgstr ""
+msgid "Helps reduce request volume (for example, from crawlers or abusive bots)"
+msgstr ""
+
msgid "Helps reduce request volume for protected paths"
msgstr ""
@@ -20604,6 +20622,9 @@ msgstr ""
msgid "Max authenticated API requests per period per user"
msgstr ""
+msgid "Max authenticated Git LFS requests per period per user"
+msgstr ""
+
msgid "Max authenticated web requests per period per user"
msgstr ""
diff --git a/rubocop/cop/migration/versioned_migration_class.rb b/rubocop/cop/migration/versioned_migration_class.rb
new file mode 100644
index 00000000000..5a2c4f11ece
--- /dev/null
+++ b/rubocop/cop/migration/versioned_migration_class.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ class VersionedMigrationClass < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ ENFORCED_SINCE = 2021_09_02_00_00_00
+
+ MSG_INHERIT = 'Don\'t inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.'
+ MSG_INCLUDE = 'Don\'t include migration helper modules directly. Inherit from Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.'
+
+ MIGRATION_CLASS = 'Gitlab::Database::Migration'
+
+ def_node_search :includes_helpers?, <<~PATTERN
+ (send nil? :include
+ (const
+ (const
+ (const nil? :Gitlab) :Database) :MigrationHelpers))
+ PATTERN
+
+ def on_class(node)
+ return unless relevant_migration?(node)
+
+ add_offense(node, location: :expression, message: MSG_INHERIT) unless gitlab_migration_class?(node)
+ end
+
+ def on_send(node)
+ return unless relevant_migration?(node)
+
+ add_offense(node, location: :expression, message: MSG_INCLUDE) if includes_helpers?(node)
+ end
+
+ private
+
+ def relevant_migration?(node)
+ in_migration?(node) && version(node) >= ENFORCED_SINCE
+ end
+
+ def gitlab_migration_class?(node)
+ superclass(node) == MIGRATION_CLASS
+ end
+
+ def superclass(class_node)
+ _, *others = class_node.descendants
+
+ others.find { |node| node.const_type? && node&.const_name != 'Types' }&.const_name
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 0f12e0e4aec..6f2c908c289 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -3,6 +3,8 @@ import Bold from '~/content_editor/extensions/bold';
import BulletList from '~/content_editor/extensions/bullet_list';
import Code from '~/content_editor/extensions/code';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
+import DescriptionItem from '~/content_editor/extensions/description_item';
+import DescriptionList from '~/content_editor/extensions/description_list';
import Division from '~/content_editor/extensions/division';
import Emoji from '~/content_editor/extensions/emoji';
import Figure from '~/content_editor/extensions/figure';
@@ -41,6 +43,8 @@ const tiptapEditor = createTestEditor({
BulletList,
Code,
CodeBlockHighlight,
+ DescriptionItem,
+ DescriptionList,
Division,
Emoji,
Figure,
@@ -75,6 +79,8 @@ const {
code,
codeBlock,
division,
+ descriptionItem,
+ descriptionList,
emoji,
figure,
figureCaption,
@@ -105,6 +111,8 @@ const {
code: { markType: Code.name },
codeBlock: { nodeType: CodeBlockHighlight.name },
division: { nodeType: Division.name },
+ descriptionItem: { nodeType: DescriptionItem.name },
+ descriptionList: { nodeType: DescriptionList.name },
emoji: { markType: Emoji.name },
figure: { nodeType: Figure.name },
figureCaption: { nodeType: FigureCaption.name },
@@ -545,6 +553,41 @@ this is not really json but just trying out whether this case works or not
);
});
+ it('correctly renders a description list', () => {
+ expect(
+ serialize(
+ descriptionList(
+ descriptionItem(paragraph('Beast of Bodmin')),
+ descriptionItem({ isTerm: false }, paragraph('A large feline inhabiting Bodmin Moor.')),
+
+ descriptionItem(paragraph('Morgawr')),
+ descriptionItem({ isTerm: false }, paragraph('A sea serpent.')),
+
+ descriptionItem(paragraph('Owlman')),
+ descriptionItem(
+ { isTerm: false },
+ paragraph('A giant ', italic('owl-like'), ' creature.'),
+ ),
+ ),
+ ),
+ ).toBe(
+ `
+<dl>
+<dt>Beast of Bodmin</dt>
+<dd>A large feline inhabiting Bodmin Moor.</dd>
+<dt>Morgawr</dt>
+<dd>A sea serpent.</dd>
+<dt>Owlman</dt>
+<dd>
+
+A giant _owl-like_ creature.
+
+</dd>
+</dl>
+ `.trim(),
+ );
+ });
+
it('correctly renders div', () => {
expect(
serialize(
diff --git a/spec/frontend/deploy_freeze/helpers.js b/spec/frontend/deploy_freeze/helpers.js
index bfb84142662..598f14d45f6 100644
--- a/spec/frontend/deploy_freeze/helpers.js
+++ b/spec/frontend/deploy_freeze/helpers.js
@@ -1,7 +1,7 @@
import { secondsToHours } from '~/lib/utils/datetime_utility';
export const freezePeriodsFixture = getJSONFixture('/api/freeze-periods/freeze_periods.json');
-export const timezoneDataFixture = getJSONFixture('/api/freeze-periods/timezone_data.json');
+export const timezoneDataFixture = getJSONFixture('/timezones/short.json');
export const findTzByName = (identifier = '') =>
timezoneDataFixture.find(({ name }) => name.toLowerCase() === identifier.toLowerCase());
diff --git a/spec/frontend/diffs/create_diffs_store.js b/spec/frontend/diffs/create_diffs_store.js
index e6a8b7a72ae..307ebdaa4ac 100644
--- a/spec/frontend/diffs/create_diffs_store.js
+++ b/spec/frontend/diffs/create_diffs_store.js
@@ -9,6 +9,12 @@ Vue.use(Vuex);
export default function createDiffsStore() {
return new Vuex.Store({
modules: {
+ page: {
+ namespaced: true,
+ state: {
+ activeTab: 'notes',
+ },
+ },
diffs: diffsModule(),
notes: notesModule(),
batchComments: batchCommentsModule(),
diff --git a/spec/frontend/fixtures/api_markdown.yml b/spec/frontend/fixtures/api_markdown.yml
index feede5458c7..1edb8cb3f41 100644
--- a/spec/frontend/fixtures/api_markdown.yml
+++ b/spec/frontend/fixtures/api_markdown.yml
@@ -59,6 +59,24 @@
</figcaption>
</figure>
+- name: description_list
+ markdown: |-
+ <dl>
+ <dt>Frog</dt>
+ <dd>Wet green thing</dd>
+ <dt>Rabbit</dt>
+ <dd>Warm fluffy thing</dd>
+ <dt>Punt</dt>
+ <dd>Kick a ball</dd>
+ <dd>Take a bet</dd>
+ <dt>Color</dt>
+ <dt>Colour</dt>
+ <dd>
+
+ Any hue except _white_ or **black**
+
+ </dd>
+ </dl>
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: attachment_link
diff --git a/spec/frontend/fixtures/freeze_period.rb b/spec/frontend/fixtures/freeze_period.rb
index 09e4f969e1d..42762fa56f9 100644
--- a/spec/frontend/fixtures/freeze_period.rb
+++ b/spec/frontend/fixtures/freeze_period.rb
@@ -39,13 +39,4 @@ RSpec.describe 'Freeze Periods (JavaScript fixtures)' do
expect(response).to be_successful
end
end
-
- describe TimeZoneHelper, '(JavaScript fixtures)' do
- let(:response) { timezone_data.to_json }
-
- it 'api/freeze-periods/timezone_data.json' do
- # Looks empty but does things
- # More info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38525/diffs#note_391048415
- end
- end
end
diff --git a/spec/frontend/fixtures/timezones.rb b/spec/frontend/fixtures/timezones.rb
new file mode 100644
index 00000000000..261dcf5e116
--- /dev/null
+++ b/spec/frontend/fixtures/timezones.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TimeZoneHelper, '(JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+ include TimeZoneHelper
+
+ let(:response) { @timezones.sort_by! { |tz| tz[:name] }.to_json }
+
+ before(:all) do
+ clean_frontend_fixtures('timezones/')
+ end
+
+ it 'timezones/short.json' do
+ @timezones = timezone_data(format: :short)
+ end
+
+ it 'timezones/full.json' do
+ @timezones = timezone_data(format: :full)
+ end
+end
diff --git a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap
index db174346729..7f655d67ae8 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/code_block_spec.js.snap
@@ -2,7 +2,7 @@
exports[`Code Block with default props renders correctly 1`] = `
<pre
- class="code-block rounded"
+ class="code-block rounded code"
>
<code
class="d-block"
@@ -14,7 +14,7 @@ exports[`Code Block with default props renders correctly 1`] = `
exports[`Code Block with maxHeight set to "200px" renders correctly 1`] = `
<pre
- class="code-block rounded"
+ class="code-block rounded code"
style="max-height: 200px; overflow-y: auto;"
>
<code
diff --git a/spec/lib/gitlab/database/migration_spec.rb b/spec/lib/gitlab/database/migration_spec.rb
new file mode 100644
index 00000000000..e062bf3e5ef
--- /dev/null
+++ b/spec/lib/gitlab/database/migration_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Migration do
+ describe '.[]' do
+ context 'version: 1.0' do
+ subject { described_class[1.0] }
+
+ it 'inherits from ActiveRecord::Migration[6.1]' do
+ expect(subject.superclass).to eq(ActiveRecord::Migration[6.1])
+ end
+
+ it 'includes migration helpers version 2' do
+ expect(subject.included_modules).to include(Gitlab::Database::MigrationHelpers::V2)
+ end
+ end
+
+ context 'unknown version' do
+ it 'raises an error' do
+ expect { described_class[0] }.to raise_error(ArgumentError, /Unknown migration version/)
+ end
+ end
+ end
+
+ describe '.current_version' do
+ it 'includes current ActiveRecord migration class' do
+ # This breaks upon Rails upgrade. In that case, we'll add a new version in Gitlab::Database::Migration::MIGRATION_CLASSES,
+ # bump .current_version and leave existing migrations and already defined versions of Gitlab::Database::Migration
+ # untouched.
+ expect(described_class[described_class.current_version].superclass).to eq(ActiveRecord::Migration::Current)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index d343634fb92..aa13660deb4 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -468,6 +468,7 @@ RSpec.describe Gitlab::PathRegex do
end
let_it_be(:git_paths) { container_paths.map { |path| path + '.git' } }
+ let_it_be(:git_lfs_paths) { git_paths.flat_map { |path| [path + '/info/lfs/', path + '/gitlab-lfs/'] } }
let_it_be(:snippet_paths) { container_paths.grep(%r{snippets/\d}) }
let_it_be(:wiki_git_paths) { (container_paths - snippet_paths).map { |path| path + '.wiki.git' } }
let_it_be(:invalid_git_paths) { invalid_paths.map { |path| path + '.git' } }
@@ -498,6 +499,15 @@ RSpec.describe Gitlab::PathRegex do
end
end
+ describe '.repository_git_lfs_route_regex' do
+ subject { %r{\A#{described_class.repository_git_lfs_route_regex}\z} }
+
+ it 'matches the expected paths' do
+ expect_route_match(git_lfs_paths)
+ expect_no_route_match(container_paths + invalid_paths + git_paths + invalid_git_paths)
+ end
+ end
+
describe '.repository_wiki_git_route_regex' do
subject { %r{\A#{described_class.repository_wiki_git_route_regex}\z} }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 71a609d8a26..851e46c7980 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -939,6 +939,8 @@ 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_authenticated_git_lfs_requests_per_period
+ throttle_authenticated_git_lfs_period_in_seconds
]
end
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index a0f9d4c11ed..32fa1381869 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -22,7 +22,9 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
throttle_unauthenticated_packages_api_requests_per_period: 100,
throttle_unauthenticated_packages_api_period_in_seconds: 1,
throttle_authenticated_packages_api_requests_per_period: 100,
- throttle_authenticated_packages_api_period_in_seconds: 1
+ throttle_authenticated_packages_api_period_in_seconds: 1,
+ throttle_authenticated_git_lfs_requests_per_period: 100,
+ throttle_authenticated_git_lfs_period_in_seconds: 1
}
end
@@ -620,6 +622,95 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
+ describe 'authenticated git lfs requests', :api do
+ let_it_be(:project) { create(:project, :internal) }
+ let_it_be(:user) { create(: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(:request_method) { 'GET' }
+ let(:throttle_setting_prefix) { 'throttle_authenticated_git_lfs' }
+ let(:git_lfs_url) { "/#{project.full_path}.git/info/lfs/locks" }
+
+ before do
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ stub_application_setting(settings_to_set)
+ end
+
+ context 'with regular login' do
+ let(:url_that_requires_authentication) { git_lfs_url }
+
+ it_behaves_like 'rate-limited web authenticated requests'
+ end
+
+ context 'with the token in the headers' do
+ let(:request_args) { [git_lfs_url, { headers: basic_auth_headers(user, token) }] }
+ let(:other_user_request_args) { [git_lfs_url, { headers: basic_auth_headers(other_user, other_user_token) }] }
+
+ it_behaves_like 'rate-limited token-authenticated requests'
+ end
+
+ context 'precedence over authenticated web throttle' do
+ before do
+ settings_to_set[:throttle_authenticated_git_lfs_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_git_lfs_period_in_seconds] = period_in_seconds
+ end
+
+ def do_request
+ get git_lfs_url, headers: basic_auth_headers(user, token)
+ end
+
+ context 'when authenticated git lfs throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_git_lfs_enabled] = true
+ end
+
+ context 'when authenticated web throttle is lower' do
+ before do
+ settings_to_set[:throttle_authenticated_web_requests_per_period] = 0
+ settings_to_set[:throttle_authenticated_web_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_web_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'ignores authenticated web 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 git lfs throttle is disabled' do
+ before do
+ settings_to_set[:throttle_authenticated_git_lfs_enabled] = false
+ end
+
+ context 'when authenticated web throttle is enabled' do
+ before do
+ settings_to_set[:throttle_authenticated_web_requests_per_period] = requests_per_period
+ settings_to_set[:throttle_authenticated_web_period_in_seconds] = period_in_seconds
+ settings_to_set[:throttle_authenticated_web_enabled] = true
+ stub_application_setting(settings_to_set)
+ end
+
+ it 'rejects requests over the authenticated web 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
+
describe 'throttle bypass header' do
let(:headers) { {} }
let(:bypass_header) { 'gitlab-bypass-rate-limiting' }
diff --git a/spec/rubocop/cop/migration/versioned_migration_class_spec.rb b/spec/rubocop/cop/migration/versioned_migration_class_spec.rb
new file mode 100644
index 00000000000..af50e5598ce
--- /dev/null
+++ b/spec/rubocop/cop/migration/versioned_migration_class_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative '../../../../rubocop/cop/migration/versioned_migration_class'
+
+RSpec.describe RuboCop::Cop::Migration::VersionedMigrationClass do
+ subject(:cop) { described_class.new }
+
+ let(:migration) do
+ <<~SOURCE
+ class TestMigration < Gitlab::Database::Migration[1.0]
+ def up
+ execute 'select 1'
+ end
+
+ def down
+ execute 'select 1'
+ end
+ end
+ SOURCE
+ end
+
+ shared_examples 'a disabled cop' do
+ it 'does not register any offenses' do
+ expect_no_offenses(migration)
+ end
+ end
+
+ context 'outside of a migration' do
+ it_behaves_like 'a disabled cop'
+ end
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+ end
+
+ context 'in an old migration' do
+ before do
+ allow(cop).to receive(:version).and_return(described_class::ENFORCED_SINCE - 5)
+ end
+
+ it_behaves_like 'a disabled cop'
+ end
+
+ context 'that is recent' do
+ before do
+ allow(cop).to receive(:version).and_return(described_class::ENFORCED_SINCE + 5)
+ end
+
+ it 'adds an offence if inheriting from ActiveRecord::Migration' do
+ expect_offense(<<~RUBY)
+ class MyMigration < ActiveRecord::Migration[6.1]
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't inherit from ActiveRecord::Migration but use Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.
+ end
+ RUBY
+ end
+
+ it 'adds an offence if including Gitlab::Database::MigrationHelpers directly' do
+ expect_offense(<<~RUBY)
+ class MyMigration < Gitlab::Database::Migration[1.0]
+ include Gitlab::Database::MigrationHelpers
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't include migration helper modules directly. Inherit from Gitlab::Database::Migration[1.0] instead. See https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning.
+ end
+ RUBY
+ end
+ end
+ end
+end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index 30f606a1cd3..dabff37bea7 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -388,6 +388,26 @@ RSpec.describe ApplicationSettings::UpdateService do
end
end
+ context 'when git lfs rate limits are passed' do
+ let(:params) do
+ {
+ throttle_authenticated_git_lfs_enabled: 1,
+ throttle_authenticated_git_lfs_period_in_seconds: 600,
+ throttle_authenticated_git_lfs_requests_per_period: 10
+ }
+ end
+
+ it 'updates git lfs throttle settings' do
+ subject.execute
+
+ application_settings.reload
+
+ expect(application_settings.throttle_authenticated_git_lfs_enabled).to be_truthy
+ expect(application_settings.throttle_authenticated_git_lfs_period_in_seconds).to eq(600)
+ expect(application_settings.throttle_authenticated_git_lfs_requests_per_period).to eq(10)
+ end
+ end
+
context 'when issues_create_limit is passed' do
let(:params) do
{
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 95817624658..9d87a231bfa 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_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs"
# * request_method
# * request_args
# * other_user_request_args
@@ -14,7 +14,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
"throttle_authenticated_api" => "throttle_authenticated_api",
"throttle_authenticated_web" => "throttle_authenticated_web",
- "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api"
+ "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api",
+ "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs"
}
end
@@ -165,7 +166,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
end
# Requires let variables:
-# * throttle_setting_prefix: "throttle_authenticated_web" or "throttle_protected_paths"
+# * throttle_setting_prefix: "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_git_lfs"
# * user
# * url_that_requires_authentication
# * request_method
@@ -176,7 +177,8 @@ RSpec.shared_examples 'rate-limited web authenticated requests' do
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
- "throttle_authenticated_web" => "throttle_authenticated_web"
+ "throttle_authenticated_web" => "throttle_authenticated_web",
+ "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs"
}
end