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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-15 18:08:45 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-15 18:08:45 +0300
commitd9e71b0d412fb9d2d7fc8b00dddac21617eaaf19 (patch)
tree704cd8a52cf1e068bcd2e82cc1a0112e9674ef5d
parent6ae38bb3b5dc719fb6a046dcbcce4671176395a2 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_editable_item.vue6
-rw-r--r--app/assets/javascripts/jobs/components/log/line.vue32
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_meta_controls.vue39
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue16
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss15
-rw-r--r--app/controllers/admin/users_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/models/bulk_import.rb16
-rw-r--r--app/models/bulk_imports/configuration.rb20
-rw-r--r--app/models/bulk_imports/entity.rb43
-rw-r--r--app/models/deployment.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--app/views/admin/application_settings/_signup.html.haml4
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml3
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/deployments/drop_older_deployments_worker.rb14
-rw-r--r--app/workers/deployments/finished_worker.rb2
-rw-r--r--app/workers/deployments/forward_deployment_worker.rb2
-rw-r--r--app/workers/deployments/success_worker.rb2
-rw-r--r--changelogs/unreleased/18324-add-links-to-urls-in-job-logs.yml5
-rw-r--r--changelogs/unreleased/216861-autosave.yml5
-rw-r--r--changelogs/unreleased/258980-enable-feature-flag-by-default-of-admin-approval-for-new-user-sign.yml5
-rw-r--r--changelogs/unreleased/267114-enable-usage_data_api-by-default-feature.yml5
-rw-r--r--changelogs/unreleased/Replace-calendar-icon-tooltip-sidebar-ab.yml5
-rw-r--r--changelogs/unreleased/jh-store_temp_import_data.yml5
-rw-r--r--changelogs/unreleased/sh-update-puma-memory-limits.yml5
-rw-r--r--config/feature_flags/development/admin_approval_for_new_user_signups.yml2
-rw-r--r--config/feature_flags/development/usage_data_api.yml4
-rw-r--r--config/locales/devise.en.yml2
-rw-r--r--db/migrate/20200922133949_create_bulk_import.rb26
-rw-r--r--db/migrate/20200925112104_create_bulk_import_configurations.rb27
-rw-r--r--db/migrate/20200925114522_create_bulk_import_entities.rb38
-rw-r--r--db/migrate/20200925153423_add_bulk_import_foreign_key_to_bulk_import_entities.rb17
-rw-r--r--db/migrate/20200925193815_add_parent_foreign_key_to_bulk_import_entities.rb17
-rw-r--r--db/migrate/20200925193906_add_namespace_foreign_key_to_bulk_import_entities.rb19
-rw-r--r--db/migrate/20200925194006_add_project_foreign_key_to_bulk_import_entities.rb19
-rw-r--r--db/schema_migrations/202009221339491
-rw-r--r--db/schema_migrations/202009251121041
-rw-r--r--db/schema_migrations/202009251145221
-rw-r--r--db/schema_migrations/202009251534231
-rw-r--r--db/schema_migrations/202009251938151
-rw-r--r--db/schema_migrations/202009251939061
-rw-r--r--db/schema_migrations/202009251940061
-rw-r--r--db/structure.sql112
-rw-r--r--doc/.vale/gitlab/Acronyms.yml1
-rw-r--r--doc/.vale/gitlab/spelling-exceptions.txt1
-rw-r--r--doc/administration/troubleshooting/sidekiq.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql56
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json177
-rw-r--r--doc/api/graphql/reference/index.md12
-rw-r--r--doc/ci/img/cf_ec2_diagram_v13_5.pngbin129095 -> 43247 bytes
-rw-r--r--doc/ci/runners/README.md25
-rw-r--r--doc/ci/variables/predefined_variables.md2
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/product_analytics/usage_ping.md2
-rw-r--r--doc/development/wikis.md94
-rw-r--r--doc/operations/incident_management/img/incident_highlight_bar_v13_5.pngbin0 -> 36177 bytes
-rw-r--r--doc/operations/incident_management/img/incident_list_v13_4.pngbin16735 -> 0 bytes
-rw-r--r--doc/operations/incident_management/img/incident_list_v13_5.pngbin0 -> 43685 bytes
-rw-r--r--doc/operations/incident_management/img/incident_sla_settings_v13_5.pngbin0 -> 21480 bytes
-rw-r--r--doc/operations/incident_management/incidents.md45
-rw-r--r--doc/subscriptions/self_managed/index.md2
-rw-r--r--doc/user/admin_area/approving_users.md36
-rw-r--r--doc/user/admin_area/credentials_inventory.md12
-rw-r--r--doc/user/admin_area/img/credentials_inventory_ssh_keys_v13_5.pngbin0 -> 26813 bytes
-rw-r--r--doc/user/admin_area/settings/img/email_confirmation_v12_7.pngbin9837 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/sign_up_restrictions_v13_5.pngbin0 -> 39902 bytes
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md11
-rw-r--r--doc/user/img/gitlab_snippet_v13_5.pngbin43640 -> 20563 bytes
-rw-r--r--lib/api/usage_data.rb2
-rw-r--r--lib/gitlab/cluster/puma_worker_killer_initializer.rb4
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--locale/gitlab.pot17
-rw-r--r--package.json1
-rw-r--r--qa/qa/page/project/pipeline/show.rb4
-rw-r--r--spec/controllers/registrations_controller_spec.rb2
-rw-r--r--spec/factories/bulk_import.rb8
-rw-r--r--spec/factories/bulk_import/entities.rb21
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js12
-rw-r--r--spec/frontend/jobs/components/log/line_spec.js65
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_controls_spec.js51
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_modal_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js2
-rw-r--r--spec/models/bulk_import_spec.rb18
-rw-r--r--spec/models/bulk_imports/configuration_spec.rb17
-rw-r--r--spec/models/bulk_imports/entity_spec.rb85
-rw-r--r--spec/models/deployment_spec.rb4
-rw-r--r--spec/workers/deployments/drop_older_deployments_worker_spec.rb18
-rw-r--r--yarn.lock5
94 files changed, 1296 insertions, 120 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index c37e923a787..593939ab55c 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0fc40ef439ae4bbf91da2a5b454dfad5cb815a17
+05cd75fb57f06f29978e6cc0da3f7bc35d85859f
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
index ec3c4e309b6..5fb7a9b210c 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
@@ -39,13 +39,15 @@ export default {
this.$emit('open');
window.addEventListener('click', this.collapseWhenOffClick);
},
- collapse() {
+ collapse({ emitEvent = true } = {}) {
if (!this.edit) {
return;
}
this.edit = false;
- this.$emit('close');
+ if (emitEvent) {
+ this.$emit('close');
+ }
window.removeEventListener('click', this.collapseWhenOffClick);
},
},
diff --git a/app/assets/javascripts/jobs/components/log/line.vue b/app/assets/javascripts/jobs/components/log/line.vue
index e68d5b8eda4..791664c05d9 100644
--- a/app/assets/javascripts/jobs/components/log/line.vue
+++ b/app/assets/javascripts/jobs/components/log/line.vue
@@ -1,6 +1,24 @@
<script>
+import linkifyHtml from 'linkifyjs/html';
+import { sanitize } from '~/lib/dompurify';
+import { isAbsolute } from '~/lib/utils/url_utility';
import LineNumber from './line_number.vue';
+const linkifyOptions = {
+ attributes: {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ rel: 'nofollow noopener',
+ },
+ className: 'gl-reset-color!',
+ defaultProtocol: 'https',
+ validate: {
+ email: false,
+ url(value) {
+ return isAbsolute(value);
+ },
+ },
+};
+
export default {
functional: true,
props: {
@@ -17,13 +35,15 @@ export default {
const { line, path } = props;
const chars = line.content.map(content => {
- return h(
- 'span',
- {
- class: ['gl-white-space-pre-wrap', content.style],
+ const linkfied = linkifyHtml(content.text, linkifyOptions);
+ return h('span', {
+ class: ['gl-white-space-pre-wrap', content.style],
+ domProps: {
+ innerHTML: sanitize(linkfied, {
+ ALLOWED_TAGS: ['a'],
+ }),
},
- content.text,
- );
+ });
});
return h('div', { class: 'js-line log-line' }, [
diff --git a/app/assets/javascripts/static_site_editor/components/edit_meta_controls.vue b/app/assets/javascripts/static_site_editor/components/edit_meta_controls.vue
index 927b52dc72a..9f75c65a316 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_meta_controls.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_meta_controls.vue
@@ -1,5 +1,6 @@
<script>
import { GlForm, GlFormGroup, GlFormInput, GlFormTextarea } from '@gitlab/ui';
+import AccessorUtilities from '~/lib/utils/accessor';
export default {
components: {
@@ -26,12 +27,47 @@ export default {
},
};
},
+ computed: {
+ editableStorageKey() {
+ return this.getId('local-storage', 'editable');
+ },
+ hasLocalStorage() {
+ return AccessorUtilities.isLocalStorageAccessSafe();
+ },
+ },
+ mounted() {
+ this.initCachedEditable();
+ this.preSelect();
+ },
methods: {
getId(type, key) {
return `sse-merge-request-meta-${type}-${key}`;
},
+ initCachedEditable() {
+ if (this.hasLocalStorage) {
+ const cachedEditable = JSON.parse(localStorage.getItem(this.editableStorageKey));
+ if (cachedEditable) {
+ this.editable = cachedEditable;
+ }
+ }
+ },
+ preSelect() {
+ this.$nextTick(() => {
+ this.$refs.title.$el.select();
+ });
+ },
+ resetCachedEditable() {
+ if (this.hasLocalStorage) {
+ window.localStorage.removeItem(this.editableStorageKey);
+ }
+ },
onUpdate() {
- this.$emit('updateSettings', { ...this.editable });
+ const payload = { ...this.editable };
+ this.$emit('updateSettings', payload);
+
+ if (this.hasLocalStorage) {
+ window.localStorage.setItem(this.editableStorageKey, JSON.stringify(payload));
+ }
},
},
};
@@ -46,6 +82,7 @@ export default {
>
<gl-form-input
:id="getId('control', 'title')"
+ ref="title"
v-model.lazy="editable.title"
type="text"
@input="onUpdate"
diff --git a/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue b/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
index aa4c0eb7f1c..4e5245bd892 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_meta_modal.vue
@@ -35,6 +35,12 @@ export default {
attributes: [{ variant: 'success' }, { disabled: this.disabled }],
};
},
+ secondaryProps() {
+ return {
+ text: __('Keep editing'),
+ attributes: [{ variant: 'default' }],
+ };
+ },
},
methods: {
hide() {
@@ -43,6 +49,13 @@ export default {
show() {
this.$refs.modal.show();
},
+ onPrimary() {
+ this.$emit('primary', this.mergeRequestMeta);
+ this.$refs.editMetaControls.resetCachedEditable();
+ },
+ onSecondary() {
+ this.hide();
+ },
onUpdateSettings(mergeRequestMeta) {
this.mergeRequestMeta = { ...mergeRequestMeta };
},
@@ -56,11 +69,14 @@ export default {
modal-id="edit-meta-modal"
:title="__('Submit your changes')"
:action-primary="primaryProps"
+ :action-secondary="secondaryProps"
size="sm"
- @primary="() => $emit('primary', mergeRequestMeta)"
+ @primary="onPrimary"
+ @secondary="onSecondary"
@hide="() => $emit('hide')"
>
<edit-meta-controls
+ ref="editMetaControls"
:title="mergeRequestMeta.title"
:description="mergeRequestMeta.description"
@updateSettings="onUpdateSettings"
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index 0c8b9a9d975..79d9ba6df57 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -97,7 +97,11 @@ export default {
</script>
<template>
- <header class="page-content-header ci-header-container" data-testid="pipeline-header-content">
+ <header
+ class="page-content-header gl-display-flex gl-min-h-7"
+ data-qa-selector="pipeline_header"
+ data-testid="ci-header-content"
+ >
<section class="header-main-content">
<ci-icon-badge :status="status" />
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
index 0ed5a050fe4..6511c8d8c31 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
@@ -1,11 +1,10 @@
<script>
-import { GlIcon } from '@gitlab/ui';
-import tooltip from '~/vue_shared/directives/tooltip';
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
export default {
name: 'CollapsedCalendarIcon',
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
@@ -41,16 +40,7 @@ export default {
</script>
<template>
- <div
- v-tooltip
- :class="containerClass"
- :title="tooltipText"
- data-container="body"
- data-placement="left"
- data-html="true"
- data-boundary="viewport"
- @click="click"
- >
+ <div v-gl-tooltip.left.viewport :class="containerClass" :title="tooltipText" @click="click">
<gl-icon v-if="showIcon" name="calendar" />
<slot>
<span> {{ text }} </span>
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 63715f7d08f..fe077b4b7e9 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -114,21 +114,6 @@
}
}
-/**
- * Pipelines / Jobs header
- */
-[data-page='projects:pipelines:show'],
-[data-page='projects:jobs:show'] {
- .ci-header-container {
- min-height: $gl-spacing-scale-7;
- display: flex;
-
- .text-center {
- padding-top: 12px;
- }
- }
-}
-
.pipelines-container .top-area .nav-controls > .btn:last-child {
float: none;
}
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index d280f158c14..b841a089e2b 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -300,7 +300,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def check_admin_approval_feature_available!
- access_denied! unless Feature.enabled?(:admin_approval_for_new_user_signups)
+ access_denied! unless Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true)
end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 037c83b062b..4da6e8ea1ae 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -216,7 +216,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def set_user_state
- return unless Feature.enabled?(:admin_approval_for_new_user_signups)
+ return unless Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true)
return unless Gitlab::CurrentSettings.require_admin_approval_after_user_signup
resource.state = BLOCKED_PENDING_APPROVAL_STATE
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
new file mode 100644
index 00000000000..cabff86a9f9
--- /dev/null
+++ b/app/models/bulk_import.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class BulkImport < ApplicationRecord
+ belongs_to :user, optional: false
+
+ has_one :configuration, class_name: 'BulkImports::Configuration'
+ has_many :entities, class_name: 'BulkImports::Entity'
+
+ validates :source_type, :status, presence: true
+
+ enum source_type: { gitlab: 0 }
+
+ state_machine :status, initial: :created do
+ state :created, value: 0
+ end
+end
diff --git a/app/models/bulk_imports/configuration.rb b/app/models/bulk_imports/configuration.rb
new file mode 100644
index 00000000000..8c3aff6f749
--- /dev/null
+++ b/app/models/bulk_imports/configuration.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class BulkImports::Configuration < ApplicationRecord
+ self.table_name = 'bulk_import_configurations'
+
+ belongs_to :bulk_import, inverse_of: :configuration, optional: false
+
+ validates :url, :access_token, length: { maximum: 255 }, presence: true
+ validates :url, public_url: { schemes: %w[http https], enforce_sanitization: true, ascii_only: true },
+ allow_nil: true
+
+ attr_encrypted :url,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm'
+ attr_encrypted :access_token,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm'
+end
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
new file mode 100644
index 00000000000..2d0bba7bccc
--- /dev/null
+++ b/app/models/bulk_imports/entity.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+class BulkImports::Entity < ApplicationRecord
+ self.table_name = 'bulk_import_entities'
+
+ belongs_to :bulk_import, optional: false
+ belongs_to :parent, class_name: 'BulkImports::Entity', optional: true
+
+ belongs_to :project, optional: true
+ belongs_to :group, foreign_key: :namespace_id, optional: true
+
+ validates :project, absence: true, if: :group
+ validates :group, absence: true, if: :project
+ validates :source_type, :source_full_path, :destination_name,
+ :destination_namespace, presence: true
+
+ validate :validate_parent_is_a_group, if: :parent
+ validate :validate_imported_entity_type
+
+ enum source_type: { group_entity: 0, project_entity: 1 }
+
+ state_machine :status, initial: :created do
+ state :created, value: 0
+ end
+
+ private
+
+ def validate_parent_is_a_group
+ unless parent.group_entity?
+ errors.add(:parent, s_('BulkImport|must be a group'))
+ end
+ end
+
+ def validate_imported_entity_type
+ if group.present? && project_entity?
+ errors.add(:group, s_('BulkImport|expected an associated Project but has an associated Group'))
+ end
+
+ if project.present? && group_entity?
+ errors.add(:project, s_('BulkImport|expected an associated Group but has an associated Project'))
+ end
+ end
+end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index bc4a23bfb68..381ba9d27d3 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -73,7 +73,7 @@ class Deployment < ApplicationRecord
next unless deployment.project.forward_deployment_enabled?
deployment.run_after_commit do
- Deployments::ForwardDeploymentWorker.perform_async(id)
+ Deployments::DropOlderDeploymentsWorker.perform_async(id)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8db449116eb..41cb15677a8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -167,6 +167,8 @@ class User < ApplicationRecord
has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
has_many :assigned_merge_requests, class_name: "MergeRequest", through: :merge_request_assignees, source: :merge_request
+ has_many :bulk_imports
+
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :term_agreements
diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml
index d614912b2e4..98b49a236a3 100644
--- a/app/views/admin/application_settings/_signup.html.haml
+++ b/app/views/admin/application_settings/_signup.html.haml
@@ -9,14 +9,14 @@
Sign-up enabled
.form-text.text-muted
= _("When enabled, any user visiting %{host} will be able to create an account.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" }
- - if Feature.enabled?(:admin_approval_for_new_user_signups)
+ - if Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true)
.form-group
.form-check
= f.check_box :require_admin_approval_after_user_signup, class: 'form-check-input'
= f.label :require_admin_approval_after_user_signup, class: 'form-check-label' do
= _('Require admin approval for new sign-ups')
.form-text.text-muted
- = _("When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by the admin before they can login. This setting is effective only if sign-ups are enabled.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" }
+ = _("When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" }
.form-group
.form-check
= f.check_box :send_user_confirmation_email, class: 'form-check-input'
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 2a5a97becaf..33faef92646 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -30,7 +30,7 @@
= link_to admin_users_path(filter: "blocked") do
= s_('AdminUsers|Blocked')
%small.badge.badge-pill= limited_counter_with_delimiter(User.blocked)
- - if Feature.enabled?(:admin_approval_for_new_user_signups)
+ - if Feature.enabled?(:admin_approval_for_new_user_signups, default_enabled: true)
= nav_link(html_options: { class: "#{active_when(params[:filter] == 'blocked_pending_approval')} filter-blocked-pending-approval" }) do
= link_to admin_users_path(filter: "blocked_pending_approval") do
= s_('AdminUsers|Pending approval')
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 4ec5bb1be30..b0317d84cdc 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -20,8 +20,7 @@
required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
= render 'template_selectors'
- if should_suggest_gitlab_ci_yml?
- .js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
- target: '#gitlab-ci-yml-selector',
+ .js-suggest-gitlab-ci-yml{ data: { target: '#gitlab-ci-yml-selector',
track_label: 'suggest_gitlab_ci_yml',
merge_request_path: params[:mr_path],
dismiss_key: @project.id,
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index cf27f4d9c27..452c201ec77 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -435,6 +435,14 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: deployment:deployments_drop_older_deployments
+ :feature_category: :continuous_delivery
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 3
+ :idempotent:
+ :tags: []
- :name: deployment:deployments_execute_hooks
:feature_category: :continuous_delivery
:has_external_dependencies:
diff --git a/app/workers/deployments/drop_older_deployments_worker.rb b/app/workers/deployments/drop_older_deployments_worker.rb
new file mode 100644
index 00000000000..d6cd92c1da4
--- /dev/null
+++ b/app/workers/deployments/drop_older_deployments_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Deployments
+ class DropOlderDeploymentsWorker # rubocop:disable Scalability/IdempotentWorker
+ include ApplicationWorker
+
+ queue_namespace :deployment
+ feature_category :continuous_delivery
+
+ def perform(deployment_id)
+ Deployments::OlderDeploymentsDropService.new(deployment_id).execute
+ end
+ end
+end
diff --git a/app/workers/deployments/finished_worker.rb b/app/workers/deployments/finished_worker.rb
index 0be420af718..62c886010a3 100644
--- a/app/workers/deployments/finished_worker.rb
+++ b/app/workers/deployments/finished_worker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# This worker is deprecated and will be removed in 14.0
+# See: https://gitlab.com/gitlab-org/gitlab/-/issues/266381
module Deployments
class FinishedWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
diff --git a/app/workers/deployments/forward_deployment_worker.rb b/app/workers/deployments/forward_deployment_worker.rb
index a6f246dbbbd..dd01fcbbafe 100644
--- a/app/workers/deployments/forward_deployment_worker.rb
+++ b/app/workers/deployments/forward_deployment_worker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# This worker is deprecated and will be removed in 14.0
+# See: https://gitlab.com/gitlab-org/gitlab/-/issues/266381
module Deployments
class ForwardDeploymentWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb
index ff1b2d80c1a..b72b107985b 100644
--- a/app/workers/deployments/success_worker.rb
+++ b/app/workers/deployments/success_worker.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# This worker is deprecated and will be removed in 14.0
+# See: https://gitlab.com/gitlab-org/gitlab/-/issues/266381
module Deployments
class SuccessWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
diff --git a/changelogs/unreleased/18324-add-links-to-urls-in-job-logs.yml b/changelogs/unreleased/18324-add-links-to-urls-in-job-logs.yml
new file mode 100644
index 00000000000..884022e7f86
--- /dev/null
+++ b/changelogs/unreleased/18324-add-links-to-urls-in-job-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Make URL links in job logs clickable
+merge_request: 40175
+author: Łukasz Groszkowski @falxcerebri
+type: added
diff --git a/changelogs/unreleased/216861-autosave.yml b/changelogs/unreleased/216861-autosave.yml
new file mode 100644
index 00000000000..235075927a6
--- /dev/null
+++ b/changelogs/unreleased/216861-autosave.yml
@@ -0,0 +1,5 @@
+---
+title: Preserve the merge request title and description in the static site editor upon modal close
+merge_request: 44512
+author:
+type: added
diff --git a/changelogs/unreleased/258980-enable-feature-flag-by-default-of-admin-approval-for-new-user-sign.yml b/changelogs/unreleased/258980-enable-feature-flag-by-default-of-admin-approval-for-new-user-sign.yml
new file mode 100644
index 00000000000..fa3abd7d356
--- /dev/null
+++ b/changelogs/unreleased/258980-enable-feature-flag-by-default-of-admin-approval-for-new-user-sign.yml
@@ -0,0 +1,5 @@
+---
+title: Introduce 'admin approvals for new user signups' feature
+merge_request: 45233
+author:
+type: added
diff --git a/changelogs/unreleased/267114-enable-usage_data_api-by-default-feature.yml b/changelogs/unreleased/267114-enable-usage_data_api-by-default-feature.yml
new file mode 100644
index 00000000000..df38561eb1a
--- /dev/null
+++ b/changelogs/unreleased/267114-enable-usage_data_api-by-default-feature.yml
@@ -0,0 +1,5 @@
+---
+title: Enable usage_data_api feature flag by default
+merge_request: 45004
+author:
+type: other
diff --git a/changelogs/unreleased/Replace-calendar-icon-tooltip-sidebar-ab.yml b/changelogs/unreleased/Replace-calendar-icon-tooltip-sidebar-ab.yml
new file mode 100644
index 00000000000..994652aacb7
--- /dev/null
+++ b/changelogs/unreleased/Replace-calendar-icon-tooltip-sidebar-ab.yml
@@ -0,0 +1,5 @@
+---
+title: Replacing vue shared tooltip on calendar icon
+merge_request: 45059
+author:
+type: other
diff --git a/changelogs/unreleased/jh-store_temp_import_data.yml b/changelogs/unreleased/jh-store_temp_import_data.yml
new file mode 100644
index 00000000000..d9df05be415
--- /dev/null
+++ b/changelogs/unreleased/jh-store_temp_import_data.yml
@@ -0,0 +1,5 @@
+---
+title: Create a set of models to store the temporary data needed for a bulk import
+merge_request: 42978
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-update-puma-memory-limits.yml b/changelogs/unreleased/sh-update-puma-memory-limits.yml
new file mode 100644
index 00000000000..65161fda80b
--- /dev/null
+++ b/changelogs/unreleased/sh-update-puma-memory-limits.yml
@@ -0,0 +1,5 @@
+---
+title: Raise Puma Worker Killer RAM limits
+merge_request: 45116
+author:
+type: changed
diff --git a/config/feature_flags/development/admin_approval_for_new_user_signups.yml b/config/feature_flags/development/admin_approval_for_new_user_signups.yml
index bbb21fcbffe..0cde210e6a0 100644
--- a/config/feature_flags/development/admin_approval_for_new_user_signups.yml
+++ b/config/feature_flags/development/admin_approval_for_new_user_signups.yml
@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43827
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258980
type: development
group: group::access
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/development/usage_data_api.yml b/config/feature_flags/development/usage_data_api.yml
index 83a08fa3c43..5c8e918521d 100644
--- a/config/feature_flags/development/usage_data_api.yml
+++ b/config/feature_flags/development/usage_data_api.yml
@@ -1,7 +1,7 @@
---
name: usage_data_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41301
-rollout_issue_url:
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267114
group: group::product analytics
type: development
-default_enabled: false
+default_enabled: true
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 297254364ac..6c6a5f7b1a1 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -45,7 +45,7 @@ en:
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
- signed_up_but_blocked_pending_approval: "You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your administrator."
+ signed_up_but_blocked_pending_approval: "You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator."
update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
updated: "Your account has been updated successfully."
sessions:
diff --git a/db/migrate/20200922133949_create_bulk_import.rb b/db/migrate/20200922133949_create_bulk_import.rb
new file mode 100644
index 00000000000..29d770d13ff
--- /dev/null
+++ b/db/migrate/20200922133949_create_bulk_import.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class CreateBulkImport < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ with_lock_retries do
+ create_table :bulk_imports do |t|
+ t.references :user, type: :integer, index: true, null: false, foreign_key: { on_delete: :cascade }
+
+ t.integer :source_type, null: false, limit: 2
+ t.integer :status, null: false, limit: 2
+
+ t.timestamps_with_timezone
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :bulk_imports
+ end
+ end
+end
diff --git a/db/migrate/20200925112104_create_bulk_import_configurations.rb b/db/migrate/20200925112104_create_bulk_import_configurations.rb
new file mode 100644
index 00000000000..b894cdeefbc
--- /dev/null
+++ b/db/migrate/20200925112104_create_bulk_import_configurations.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class CreateBulkImportConfigurations < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :bulk_import_configurations, if_not_exists: true do |t|
+ t.references :bulk_import, type: :integer, index: true, null: false, foreign_key: { on_delete: :cascade }
+
+ t.text :encrypted_url # rubocop: disable Migration/AddLimitToTextColumns
+ t.text :encrypted_url_iv # rubocop: disable Migration/AddLimitToTextColumns
+
+ t.text :encrypted_access_token # rubocop: disable Migration/AddLimitToTextColumns
+ t.text :encrypted_access_token_iv # rubocop: disable Migration/AddLimitToTextColumns
+
+ t.timestamps_with_timezone
+ end
+ end
+
+ def down
+ drop_table :bulk_import_configurations
+ end
+end
diff --git a/db/migrate/20200925114522_create_bulk_import_entities.rb b/db/migrate/20200925114522_create_bulk_import_entities.rb
new file mode 100644
index 00000000000..c78c4aee9ae
--- /dev/null
+++ b/db/migrate/20200925114522_create_bulk_import_entities.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class CreateBulkImportEntities < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ create_table :bulk_import_entities, if_not_exists: true do |t|
+ t.bigint :bulk_import_id, index: true, null: false
+ t.bigint :parent_id, index: true
+ t.bigint :namespace_id, index: true
+ t.bigint :project_id, index: true
+
+ t.integer :source_type, null: false, limit: 2
+ t.text :source_full_path, null: false
+
+ t.text :destination_name, null: false
+ t.text :destination_namespace, null: false
+
+ t.integer :status, null: false, limit: 2
+ t.text :jid
+
+ t.timestamps_with_timezone
+ end
+
+ add_text_limit(:bulk_import_entities, :source_full_path, 255)
+ add_text_limit(:bulk_import_entities, :destination_name, 255)
+ add_text_limit(:bulk_import_entities, :destination_namespace, 255)
+ add_text_limit(:bulk_import_entities, :jid, 255)
+ end
+
+ def down
+ drop_table :bulk_import_entities
+ end
+end
diff --git a/db/migrate/20200925153423_add_bulk_import_foreign_key_to_bulk_import_entities.rb b/db/migrate/20200925153423_add_bulk_import_foreign_key_to_bulk_import_entities.rb
new file mode 100644
index 00000000000..fca4070d990
--- /dev/null
+++ b/db/migrate/20200925153423_add_bulk_import_foreign_key_to_bulk_import_entities.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddBulkImportForeignKeyToBulkImportEntities < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :bulk_import_entities, :bulk_imports, column: :bulk_import_id, on_delete: :cascade
+ end
+
+ def down
+ remove_foreign_key :bulk_import_entities, column: :bulk_import_id
+ end
+end
diff --git a/db/migrate/20200925193815_add_parent_foreign_key_to_bulk_import_entities.rb b/db/migrate/20200925193815_add_parent_foreign_key_to_bulk_import_entities.rb
new file mode 100644
index 00000000000..37e38c384b8
--- /dev/null
+++ b/db/migrate/20200925193815_add_parent_foreign_key_to_bulk_import_entities.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddParentForeignKeyToBulkImportEntities < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :bulk_import_entities, :bulk_import_entities, column: :parent_id, on_delete: :cascade
+ end
+
+ def down
+ remove_foreign_key :bulk_import_entities, column: :parent_id
+ end
+end
diff --git a/db/migrate/20200925193906_add_namespace_foreign_key_to_bulk_import_entities.rb b/db/migrate/20200925193906_add_namespace_foreign_key_to_bulk_import_entities.rb
new file mode 100644
index 00000000000..13212395488
--- /dev/null
+++ b/db/migrate/20200925193906_add_namespace_foreign_key_to_bulk_import_entities.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddNamespaceForeignKeyToBulkImportEntities < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :bulk_import_entities, :namespaces, column: :namespace_id
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :bulk_import_entities, column: :namespace_id
+ end
+ end
+end
diff --git a/db/migrate/20200925194006_add_project_foreign_key_to_bulk_import_entities.rb b/db/migrate/20200925194006_add_project_foreign_key_to_bulk_import_entities.rb
new file mode 100644
index 00000000000..975f2b1ef4a
--- /dev/null
+++ b/db/migrate/20200925194006_add_project_foreign_key_to_bulk_import_entities.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddProjectForeignKeyToBulkImportEntities < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :bulk_import_entities, :projects, column: :project_id
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :bulk_import_entities, column: :project_id
+ end
+ end
+end
diff --git a/db/schema_migrations/20200922133949 b/db/schema_migrations/20200922133949
new file mode 100644
index 00000000000..8c1874198bb
--- /dev/null
+++ b/db/schema_migrations/20200922133949
@@ -0,0 +1 @@
+8196e28f6fe8cdb4cf710922b5cd218030ba587c629de7ee75dc061d05c7e1a9 \ No newline at end of file
diff --git a/db/schema_migrations/20200925112104 b/db/schema_migrations/20200925112104
new file mode 100644
index 00000000000..bb0f8032a84
--- /dev/null
+++ b/db/schema_migrations/20200925112104
@@ -0,0 +1 @@
+a14df9e9a115d39636b29bfe73fb175bb1e8d4510bee26e3e0c6c979949b13c4 \ No newline at end of file
diff --git a/db/schema_migrations/20200925114522 b/db/schema_migrations/20200925114522
new file mode 100644
index 00000000000..4b8d893833d
--- /dev/null
+++ b/db/schema_migrations/20200925114522
@@ -0,0 +1 @@
+7d43d2fa91e27eaf9399cf0ce9e4375e849deb71b12d4891455bc51392bce14a \ No newline at end of file
diff --git a/db/schema_migrations/20200925153423 b/db/schema_migrations/20200925153423
new file mode 100644
index 00000000000..ffbdc2c81c2
--- /dev/null
+++ b/db/schema_migrations/20200925153423
@@ -0,0 +1 @@
+f445704e51dad2369719d8c0931c3314793fa90ba6b5a383df503ea4f6dafd20 \ No newline at end of file
diff --git a/db/schema_migrations/20200925193815 b/db/schema_migrations/20200925193815
new file mode 100644
index 00000000000..fdfa07c5a99
--- /dev/null
+++ b/db/schema_migrations/20200925193815
@@ -0,0 +1 @@
+a814b745b4911fc6c80971e6c0c19e6d64ca30cb94fa87a94bc1adf8c07b1c87 \ No newline at end of file
diff --git a/db/schema_migrations/20200925193906 b/db/schema_migrations/20200925193906
new file mode 100644
index 00000000000..dd9c1e4cd3b
--- /dev/null
+++ b/db/schema_migrations/20200925193906
@@ -0,0 +1 @@
+a915ccf5df0ec803286205916ffcd34b1410d1cc4da84f8299b63b3665d69e09 \ No newline at end of file
diff --git a/db/schema_migrations/20200925194006 b/db/schema_migrations/20200925194006
new file mode 100644
index 00000000000..fc89ebb9f7b
--- /dev/null
+++ b/db/schema_migrations/20200925194006
@@ -0,0 +1 @@
+28a71a380be0ef08389defac604c351f0a7f31b6c03a7c40aabe47bf09e6a485 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 602622e6418..12db9e6e802 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9816,6 +9816,73 @@ CREATE SEQUENCE broadcast_messages_id_seq
ALTER SEQUENCE broadcast_messages_id_seq OWNED BY broadcast_messages.id;
+CREATE TABLE bulk_import_configurations (
+ id bigint NOT NULL,
+ bulk_import_id integer NOT NULL,
+ encrypted_url text,
+ encrypted_url_iv text,
+ encrypted_access_token text,
+ encrypted_access_token_iv text,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE bulk_import_configurations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE bulk_import_configurations_id_seq OWNED BY bulk_import_configurations.id;
+
+CREATE TABLE bulk_import_entities (
+ id bigint NOT NULL,
+ bulk_import_id bigint NOT NULL,
+ parent_id bigint,
+ namespace_id bigint,
+ project_id bigint,
+ source_type smallint NOT NULL,
+ source_full_path text NOT NULL,
+ destination_name text NOT NULL,
+ destination_namespace text NOT NULL,
+ status smallint NOT NULL,
+ jid text,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ CONSTRAINT check_13f279f7da CHECK ((char_length(source_full_path) <= 255)),
+ CONSTRAINT check_715d725ea2 CHECK ((char_length(destination_name) <= 255)),
+ CONSTRAINT check_796a4d9cc6 CHECK ((char_length(jid) <= 255)),
+ CONSTRAINT check_b834fff4d9 CHECK ((char_length(destination_namespace) <= 255))
+);
+
+CREATE SEQUENCE bulk_import_entities_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE bulk_import_entities_id_seq OWNED BY bulk_import_entities.id;
+
+CREATE TABLE bulk_imports (
+ id bigint NOT NULL,
+ user_id integer NOT NULL,
+ source_type smallint NOT NULL,
+ status smallint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL
+);
+
+CREATE SEQUENCE bulk_imports_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE bulk_imports_id_seq OWNED BY bulk_imports.id;
+
CREATE TABLE chat_names (
id integer NOT NULL,
user_id integer NOT NULL,
@@ -17304,6 +17371,12 @@ ALTER TABLE ONLY boards_epic_user_preferences ALTER COLUMN id SET DEFAULT nextva
ALTER TABLE ONLY broadcast_messages ALTER COLUMN id SET DEFAULT nextval('broadcast_messages_id_seq'::regclass);
+ALTER TABLE ONLY bulk_import_configurations ALTER COLUMN id SET DEFAULT nextval('bulk_import_configurations_id_seq'::regclass);
+
+ALTER TABLE ONLY bulk_import_entities ALTER COLUMN id SET DEFAULT nextval('bulk_import_entities_id_seq'::regclass);
+
+ALTER TABLE ONLY bulk_imports ALTER COLUMN id SET DEFAULT nextval('bulk_imports_id_seq'::regclass);
+
ALTER TABLE ONLY chat_names ALTER COLUMN id SET DEFAULT nextval('chat_names_id_seq'::regclass);
ALTER TABLE ONLY chat_teams ALTER COLUMN id SET DEFAULT nextval('chat_teams_id_seq'::regclass);
@@ -18280,6 +18353,15 @@ ALTER TABLE ONLY boards
ALTER TABLE ONLY broadcast_messages
ADD CONSTRAINT broadcast_messages_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY bulk_import_configurations
+ ADD CONSTRAINT bulk_import_configurations_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY bulk_import_entities
+ ADD CONSTRAINT bulk_import_entities_pkey PRIMARY KEY (id);
+
+ALTER TABLE ONLY bulk_imports
+ ADD CONSTRAINT bulk_imports_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY chat_names
ADD CONSTRAINT chat_names_pkey PRIMARY KEY (id);
@@ -19785,6 +19867,18 @@ CREATE INDEX index_boards_on_project_id ON boards USING btree (project_id);
CREATE INDEX index_broadcast_message_on_ends_at_and_broadcast_type_and_id ON broadcast_messages USING btree (ends_at, broadcast_type, id);
+CREATE INDEX index_bulk_import_configurations_on_bulk_import_id ON bulk_import_configurations USING btree (bulk_import_id);
+
+CREATE INDEX index_bulk_import_entities_on_bulk_import_id ON bulk_import_entities USING btree (bulk_import_id);
+
+CREATE INDEX index_bulk_import_entities_on_namespace_id ON bulk_import_entities USING btree (namespace_id);
+
+CREATE INDEX index_bulk_import_entities_on_parent_id ON bulk_import_entities USING btree (parent_id);
+
+CREATE INDEX index_bulk_import_entities_on_project_id ON bulk_import_entities USING btree (project_id);
+
+CREATE INDEX index_bulk_imports_on_user_id ON bulk_imports USING btree (user_id);
+
CREATE UNIQUE INDEX index_chat_names_on_service_id_and_team_id_and_chat_id ON chat_names USING btree (service_id, team_id, chat_id);
CREATE UNIQUE INDEX index_chat_names_on_user_id_and_service_id ON chat_names USING btree (user_id, service_id);
@@ -22415,6 +22509,9 @@ ALTER TABLE ONLY ci_builds
ALTER TABLE ONLY vulnerabilities
ADD CONSTRAINT fk_88b4d546ef FOREIGN KEY (start_date_sourcing_milestone_id) REFERENCES milestones(id) ON DELETE SET NULL;
+ALTER TABLE ONLY bulk_import_entities
+ ADD CONSTRAINT fk_88c725229f FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_899c8f3231 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -22493,6 +22590,9 @@ ALTER TABLE ONLY ci_builds
ALTER TABLE ONLY ci_pipelines
ADD CONSTRAINT fk_a23be95014 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_import_entities
+ ADD CONSTRAINT fk_a44ff95be5 FOREIGN KEY (parent_id) REFERENCES bulk_import_entities(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY users
ADD CONSTRAINT fk_a4b8fefe3e FOREIGN KEY (managing_group_id) REFERENCES namespaces(id) ON DELETE SET NULL;
@@ -22535,6 +22635,9 @@ ALTER TABLE ONLY project_access_tokens
ALTER TABLE ONLY protected_tag_create_access_levels
ADD CONSTRAINT fk_b4eb82fe3c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_import_entities
+ ADD CONSTRAINT fk_b69fa2b2df FOREIGN KEY (bulk_import_id) REFERENCES bulk_imports(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY compliance_management_frameworks
ADD CONSTRAINT fk_b74c45b71f FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -22595,6 +22698,9 @@ ALTER TABLE ONLY todos
ALTER TABLE ONLY geo_event_log
ADD CONSTRAINT fk_cff7185ad2 FOREIGN KEY (reset_checksum_event_id) REFERENCES geo_reset_checksum_events(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_import_entities
+ ADD CONSTRAINT fk_d06d023c30 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY project_mirror_data
ADD CONSTRAINT fk_d1aad367d7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -22835,6 +22941,9 @@ ALTER TABLE ONLY project_statistics
ALTER TABLE ONLY user_details
ADD CONSTRAINT fk_rails_12e0b3043d FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_imports
+ ADD CONSTRAINT fk_rails_130a09357d FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY diff_note_positions
ADD CONSTRAINT fk_rails_13c7212859 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
@@ -23147,6 +23256,9 @@ ALTER TABLE ONLY status_page_settings
ALTER TABLE ONLY project_repository_storage_moves
ADD CONSTRAINT fk_rails_5106dbd44a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY bulk_import_configurations
+ ADD CONSTRAINT fk_rails_536b96bff1 FOREIGN KEY (bulk_import_id) REFERENCES bulk_imports(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY x509_commit_signatures
ADD CONSTRAINT fk_rails_53fe41188f FOREIGN KEY (x509_certificate_id) REFERENCES x509_certificates(id) ON DELETE CASCADE;
diff --git a/doc/.vale/gitlab/Acronyms.yml b/doc/.vale/gitlab/Acronyms.yml
index 4cf180e2213..53690138300 100644
--- a/doc/.vale/gitlab/Acronyms.yml
+++ b/doc/.vale/gitlab/Acronyms.yml
@@ -66,6 +66,7 @@ exceptions:
- POST
- PUT
- RAM
+ - REST
- RPC
- RSA
- RSS
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index 0f8bf0793b0..cf5f6b55caf 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -167,6 +167,7 @@ Gitleaks
Gitter
globals
Gmail
+Gollum
Google
Gosec
Gradle
diff --git a/doc/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 404e806c5d9..b7762f8ac3e 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -7,7 +7,7 @@ may be filling up. Users will notice when this happens because new branches
may not show up and merge requests may not be updated. The following are some
troubleshooting steps that will help you diagnose the bottleneck.
-NOTE **Note:**
+NOTE: **Note:**
GitLab administrators/users should consider working through these
debug steps with GitLab Support so the backtraces can be analyzed by our team.
It may reveal a bug or necessary improvement in GitLab.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 10ffa24820a..524217c5811 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -4400,6 +4400,61 @@ enum DastSiteProfileValidationStatusEnum {
}
"""
+Autogenerated input type of DastSiteTokenCreate
+"""
+input DastSiteTokenCreateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ The project the site token belongs to.
+ """
+ fullPath: ID!
+
+ """
+ The URL of the target to be validated.
+ """
+ targetUrl: String
+}
+
+"""
+Autogenerated return type of DastSiteTokenCreate
+"""
+type DastSiteTokenCreatePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ ID of the site token.
+ """
+ id: DastSiteTokenID
+
+ """
+ The current validation status of the target.
+ """
+ status: DastSiteProfileValidationStatusEnum
+
+ """
+ Token string.
+ """
+ token: String
+}
+
+"""
+Identifier of DastSiteToken
+"""
+scalar DastSiteTokenID
+
+"""
Date represented in ISO 8601
"""
scalar Date
@@ -12092,6 +12147,7 @@ type Mutation {
dastSiteProfileCreate(input: DastSiteProfileCreateInput!): DastSiteProfileCreatePayload
dastSiteProfileDelete(input: DastSiteProfileDeleteInput!): DastSiteProfileDeletePayload
dastSiteProfileUpdate(input: DastSiteProfileUpdateInput!): DastSiteProfileUpdatePayload
+ dastSiteTokenCreate(input: DastSiteTokenCreateInput!): DastSiteTokenCreatePayload
deleteAnnotation(input: DeleteAnnotationInput!): DeleteAnnotationPayload
designManagementDelete(input: DesignManagementDeleteInput!): DesignManagementDeletePayload
designManagementMove(input: DesignManagementMoveInput!): DesignManagementMovePayload
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 59f223c1741..fd8bb5b24c3 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -11871,6 +11871,156 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "DastSiteTokenCreateInput",
+ "description": "Autogenerated input type of DastSiteTokenCreate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "fullPath",
+ "description": "The project the site token belongs to.",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "targetUrl",
+ "description": "The URL of the target to be validated.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "DastSiteTokenCreatePayload",
+ "description": "Autogenerated return type of DastSiteTokenCreate",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "id",
+ "description": "ID of the site token.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "DastSiteTokenID",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "status",
+ "description": "The current validation status of the target.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "ENUM",
+ "name": "DastSiteProfileValidationStatusEnum",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "token",
+ "description": "Token string.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "SCALAR",
+ "name": "DastSiteTokenID",
+ "description": "Identifier of DastSiteToken",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "SCALAR",
"name": "Date",
"description": "Date represented in ISO 8601",
@@ -34062,6 +34212,33 @@
"deprecationReason": null
},
{
+ "name": "dastSiteTokenCreate",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "DastSiteTokenCreateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "DastSiteTokenCreatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "deleteAnnotation",
"description": null,
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 864c1f0e2d3..45448f6d906 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -704,6 +704,18 @@ Autogenerated return type of DastSiteProfileUpdate.
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `id` | DastSiteProfileID | ID of the site profile. |
+### DastSiteTokenCreatePayload
+
+Autogenerated return type of DastSiteTokenCreate.
+
+| Field | Type | Description |
+| ----- | ---- | ----------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `id` | DastSiteTokenID | ID of the site token. |
+| `status` | DastSiteProfileValidationStatusEnum | The current validation status of the target. |
+| `token` | String | Token string. |
+
### DeleteAnnotationPayload
Autogenerated return type of DeleteAnnotation.
diff --git a/doc/ci/img/cf_ec2_diagram_v13_5.png b/doc/ci/img/cf_ec2_diagram_v13_5.png
index c4e079bc8d9..1d49790c7b9 100644
--- a/doc/ci/img/cf_ec2_diagram_v13_5.png
+++ b/doc/ci/img/cf_ec2_diagram_v13_5.png
Binary files differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index afcbd68aaf5..a3cc46f59bf 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -18,16 +18,12 @@ Runners can be specific to certain projects or available to all projects.
## Types of runners
-There are three types of runners:
+In the GitLab UI there are three types of runners, based on who you want to have access:
-- [Shared](#shared-runners) (for all projects)
-- [Group](#group-runners) (for all projects in a group)
-- [Specific](#specific-runners) (for specific projects)
-
-If you are running self-managed GitLab, you can create your own runners.
-
-If you are using GitLab.com, you can use the shared runners provided by GitLab or
-create your own group or specific runners.
+- [Shared runners](#shared-runners) are available to all groups and projects in a GitLab instance.
+- [Group runners](#group-runners) are available to all projects and subgroups in a group.
+- [Specific runners](#specific-runners) are associated with specific projects.
+ Typically, specific runners are used for one project at a time.
### Shared runners
@@ -122,21 +118,20 @@ To enable shared runners:
#### Disable shared runners
-You can disable shared runners for individual projects<!-- or for groups-->.
-You must have Owner permissions for the project<!-- or group-->.
+You can disable shared runners for individual projects or for groups.
+You must have Owner permissions for the project or group.
To disable shared runners for a project:
1. Go to the project's **Settings > CI/CD** and expand the **Runners** section.
1. In the **Shared runners** area, click **Disable shared runners**.
-<!--To disable shared runners for a group:
+To disable shared runners for a group:
1. Go to the group's **Settings > CI/CD** and expand the **Runners** section.
-1. In the **Shared runners** area, click **Disable shared runners globally**.
+1. In the **Shared runners** area, click **Enable shared runners for this group**.
1. Optionally, to allow shared runners to be enabled for individual projects or subgroups,
- click **Allow projects/subgroups to override the global setting**.
--->
+ click **Allow projects and subgroups to override the group setting**.
### Group runners
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 7341c7a917d..08aaacd2620 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -105,7 +105,7 @@ Kubernetes-specific environment variables are detailed in the
| `CI_PROJECT_ID` | all | all | The unique ID of the current project that GitLab CI/CD uses internally |
| `CI_PROJECT_NAME` | 8.10 | 0.5 | The name of the directory for the project that is currently being built. For example, if the project URL is `gitlab.example.com/group-name/project-1`, the `CI_PROJECT_NAME` would be `project-1`. |
| `CI_PROJECT_NAMESPACE` | 8.10 | 0.5 | The project namespace (username or group name) that is currently being built |
-| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is currently being built. For example, if `CI_PROJECT_NAME` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
+| `CI_PROJECT_ROOT_NAMESPACE` | 13.2 | 0.5 | The **root** project namespace (username or group name) that is currently being built. For example, if `CI_PROJECT_NAMESPACE` is `root-group/child-group/grandchild-group`, `CI_PROJECT_ROOT_NAMESPACE` would be `root-group`. |
| `CI_PROJECT_PATH` | 8.10 | 0.5 | The namespace with project name |
| `CI_PROJECT_PATH_SLUG` | 9.3 | all | `$CI_PROJECT_PATH` lowercased and with everything except `0-9` and `a-z` replaced with `-`. Use in URLs and domain names. |
| `CI_PROJECT_REPOSITORY_LANGUAGES` | 12.3 | all | Comma-separated, lowercased list of the languages used in the repository (e.g. `ruby,javascript,html,css`) |
diff --git a/doc/development/README.md b/doc/development/README.md
index 74f484a5c7d..7b8a5cd5f75 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -116,6 +116,7 @@ Complementary reads:
- [Code Intelligence](code_intelligence/index.md)
- [Approval Rules](approval_rules.md)
- [Feature categorization](feature_categorization/index.md)
+- [Wikis development guide](wikis.md)
## Performance guides
diff --git a/doc/development/product_analytics/usage_ping.md b/doc/development/product_analytics/usage_ping.md
index 7c8a62e2528..c988cabfabe 100644
--- a/doc/development/product_analytics/usage_ping.md
+++ b/doc/development/product_analytics/usage_ping.md
@@ -387,7 +387,7 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
Increment unique users count using Redis HLL, for given event name.
- Tracking events using the `UsageData` API requires the `usage_data_api` feature flag to be enabled, which is disabled by default.
+ Tracking events using the `UsageData` API requires the `usage_data_api` feature flag to be enabled, which is enabled by default.
API requests are protected by checking for a valid CSRF token.
diff --git a/doc/development/wikis.md b/doc/development/wikis.md
new file mode 100644
index 00000000000..9a436762645
--- /dev/null
+++ b/doc/development/wikis.md
@@ -0,0 +1,94 @@
+---
+type: reference, dev
+stage: none
+group: Development
+info: "See the Technical Writers assigned to Development Guidelines: https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines"
+description: "GitLab's development guidelines for Wikis"
+---
+
+# Wikis development guide
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227027) in GitLab 13.5.
+
+## Overview
+
+The wiki functionality in GitLab is based on [Gollum 4.x](https://github.com/gollum/gollum/),
+which is used in [Gitaly's](gitaly.md) Ruby service and accessed from the Rails app through Gitaly RPC calls.
+
+Wikis use Git repositories as storage backend, and can be accessed through:
+
+- The [Web UI](../user/project/wiki/index.md)
+- The [REST API](../api/wikis.md)
+- [Git itself](../user/project/wiki/#adding-and-editing-wiki-pages-locally)
+
+[Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2214) in GitLab 13.5, wikis are also available
+for groups, in addition to projects.
+
+## Involved Gems
+
+Some notable gems that are used for wikis are:
+
+| Component | Description | Gem name | GitLab project | Upstream project |
+|:--------------|:-----------------------------------------------|:-------------------------------|:--------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------|
+| `gitlab` | Markup renderer, depends on various other gems | `gitlab-markup` | [`gitlab-org/gitlab-markup`](https://gitlab.com/gitlab-org/gitlab-markup) | [`github/markup`](https://github.com/github/markup) |
+| `gitaly-ruby` | Main Gollum library | `gitlab-gollum-lib` | [`gitlab-org/gollum-lib`](https://gitlab.com/gitlab-org/gollum-lib) | [`gollum/gollum-lib`](https://github.com/gollum/gollum-lib) |
+| | Gollum Git adapter for Rugged | `gitlab-gollum-rugged_adapter` | [`gitlab-org/gitlab-gollum-rugged_adapter`](https://gitlab.com/gitlab-org/gitlab-gollum-rugged_adapter) | [`gollum/rugged_adapter`](https://github.com/gollum/rugged_adapter) |
+| | Rugged (also used in Gitaly itself) | `rugged` | - | [`libgit2/rugged`](https://github.com/libgit2/rugged) |
+
+### Notes on Gollum
+
+We only use Gollum as a storage abstraction layer, to handle the mapping between wiki page slugs and files in the repository.
+
+When rendering wiki pages, we don't use Gollum at all and instead go through a
+[custom Banzai pipeline](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/banzai/pipeline/wiki_pipeline.rb).
+This adds some [wiki-specific markup](../user/markdown.md#wiki-specific-markdown), such as Gollum's `[[link]]` syntax.
+
+Since we do not make use of most of Gollum's features, we plan to move away from it entirely at some point.
+[See this epic](https://gitlab.com/groups/gitlab-org/-/epics/2381) for reference.
+
+## Model classes
+
+The `Wiki` class is the main abstraction around a wiki repository, it needs to be initialized
+with a container which can be either a `Project` or `Group`:
+
+```mermaid
+classDiagram
+ Wiki --> ProjectWiki
+ Wiki --> GroupWiki
+
+ class Wiki {
+ #container
+ #repository
+ }
+
+ class ProjectWiki {
+ #project → #container
+ }
+
+ class GroupWiki {
+ #group → #container
+ }
+```
+
+Some models wrap similar classes from Gitaly and Gollum:
+
+| Rails Model | Gitaly Class | Gollum |
+|:------------|:--------------------------------------------------------|:---------------|
+| `Wiki` | `Gitlab::Git::Wiki` | `Gollum::Wiki` |
+| `WikiPage` | `Gitlab::Git::WikiPage`, `Gitlab::Git::WikiPageVersion` | `Gollum::Page` |
+| | `Gitlab::Git::WikiFile` | `Gollum::File` |
+
+Only some data is persisted in the database:
+
+| Model | Description |
+|:----------------------|:-----------------------------------------|
+| `WikiPage::Meta` | Metadata for wiki pages |
+| `WikiPage::Slug` | Current and previous slugs of wiki pages |
+| `ProjectRepository` | Gitaly storage data for project wikis |
+| `GroupWikiRepository` | Gitaly storage data for group wikis |
+
+## Attachments
+
+The web UI uploads attachments through the REST API, which stores the files as commits in the wiki repository.
+
+Prior to GitLab 11.3 attachments were stored outside of the repository, [see this issue](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/33475).
diff --git a/doc/operations/incident_management/img/incident_highlight_bar_v13_5.png b/doc/operations/incident_management/img/incident_highlight_bar_v13_5.png
new file mode 100644
index 00000000000..6a40e97820c
--- /dev/null
+++ b/doc/operations/incident_management/img/incident_highlight_bar_v13_5.png
Binary files differ
diff --git a/doc/operations/incident_management/img/incident_list_v13_4.png b/doc/operations/incident_management/img/incident_list_v13_4.png
deleted file mode 100644
index bf00e630c67..00000000000
--- a/doc/operations/incident_management/img/incident_list_v13_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/incident_management/img/incident_list_v13_5.png b/doc/operations/incident_management/img/incident_list_v13_5.png
new file mode 100644
index 00000000000..88942a70e88
--- /dev/null
+++ b/doc/operations/incident_management/img/incident_list_v13_5.png
Binary files differ
diff --git a/doc/operations/incident_management/img/incident_sla_settings_v13_5.png b/doc/operations/incident_management/img/incident_sla_settings_v13_5.png
new file mode 100644
index 00000000000..94c8b840210
--- /dev/null
+++ b/doc/operations/incident_management/img/incident_sla_settings_v13_5.png
Binary files differ
diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md
index e6eda180eb5..b45ad3daefc 100644
--- a/doc/operations/incident_management/incidents.md
+++ b/doc/operations/incident_management/incidents.md
@@ -13,7 +13,7 @@ For users with at least Guest [permissions](../../user/permissions.md), the
Incident Management list is available at **Operations > Incidents**
in your project's sidebar. The list contains the following metrics:
-![Incident List](img/incident_list_v13_4.png)
+![Incident List](img/incident_list_v13_5.png)
- **Status** - To filter incidents by their status, click **Open**, **Closed**,
or **All** above the incident list.
@@ -35,6 +35,8 @@ in your project's sidebar. The list contains the following metrics:
- **Date created** - How long ago the incident was created. This field uses the
standard GitLab pattern of `X time ago`, but is supported by a granular date/time
tooltip depending on the user's locale.
+- **Time to SLA** - If configured for this alert, the time remaining
+ [before the SLA period expires](#service-level-agreement-countdown-timer).
- **Assignees** - The user assigned to the incident.
- **Published** - Displays a green check mark (**{check-circle}**) if the incident is published
to a [Status Page](status_page.md). **(ULTIMATE)**
@@ -83,7 +85,9 @@ when you receive notification that the alert is resolved.
## Create an incident manually
-If you have at least Guest [permissions](../../user/permissions.md), to create an Incident, you have two options.
+If you have at least Guest [permissions](../../user/permissions.md), to create an Incident,
+you can create incidents manually [from the Incidents list](#from-the-incidents-list)
+or [from the issues list](#from-the-issues-list).
### From the Incidents List
@@ -152,9 +156,12 @@ The summary section for incidents provides both critical details about and the
contents of the issue template (if one was used). The highlighted bar at the top
of the incident displays from left to right:
+![Incident SLA Highlight Bar](./img/incident_highlight_bar_v13_5.png)
+
- The link to the original alert.
- The alert start time.
- The event count.
+- The time to [SLA breach](#service-level-agreement-countdown-timer).
Beneath the highlight bar, GitLab displays a summary that includes the following fields:
@@ -168,10 +175,10 @@ Comments are displayed in threads, but can be displayed chronologically
### Alert details
-Incidents show the details of linked alerts in a separate tab. To populate this
+The **Alert details** tab displays more detailed information about linked alerts. To populate this
tab, the incident must have been created with a linked alert. Incidents
[created automatically](#configure-incidents) from alerts have this
-field populated.
+field populated:
![Incident alert details](./img/incident_alert_details_v13_4.png)
@@ -187,9 +194,29 @@ un-threaded and ordered chronologically, newest to oldest:
### Service Level Agreement countdown timer
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241663) in GitLab 13.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241663) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.5.
+
+You can configure a Service Level Agreement (SLA) timer for Incidents in the Incident
+Management configuration. When set, the incident list and [incident summary tab](#summary)
+display an SLA timer for newly-created incidents, showing the time remaining
+before the SLA period expires.
+
+If the incident is not closed before the SLA period ends, GitLab adds a `missed::SLA`
+label to the incident.
+
+#### Enable the Incident SLA
+
+To configure the Service Level Agreement countdown timer:
+
+1. Sign in as a user with Maintainer [permissions](../../user/permissions.md).
+1. Navigate to **Settings > Operations** and expand **Incidents**.
+1. Select the **Incident Settings** tab:
+
+ ![Incident SLA settings](./img/incident_sla_settings_v13_5.png)
+
+1. Select the **Activate** check box to activate the integration.
+1. In **Time limit**, select the length of the SLA period.
+1. Click **Save changes**.
-After enabling **Incident SLA** in the Incident Management configuration, newly-created
-incidents display a SLA (Service Level Agreement) timer showing the time remaining before
-the SLA period expires. If the incident is not closed before the SLA period ends, GitLab
-adds a `missed::SLA` label to the incident.
+Any changes to the Incident SLA settings are applied to *new* incidents only.
+Existing incidents remain unchanged.
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index 6b65a4a9e5e..5f232bd4ed2 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -25,7 +25,7 @@ using [Seat Link](#seat-link).
Every occupied seat is counted in the subscription, with the following exceptions:
-- [Deactivated](../../user/admin_area/activating_deactivating_users.md#deactivating-a-user) and
+- [Deactivated](../../user/admin_area/activating_deactivating_users.md#deactivating-a-user), [pending approval](../../user/admin_area/approving_users.md) and
[blocked](../../user/admin_area/blocking_unblocking_users.md) users who are restricted prior to the
renewal of a subscription won't be counted as active users for the renewal subscription. They may
count as active users in the subscription period in which they were originally added.
diff --git a/doc/user/admin_area/approving_users.md b/doc/user/admin_area/approving_users.md
new file mode 100644
index 00000000000..a8537666581
--- /dev/null
+++ b/doc/user/admin_area/approving_users.md
@@ -0,0 +1,36 @@
+---
+stage: Manage
+group: Access
+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/#designated-technical-writers
+type: howto
+---
+
+# Users pending approval
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
+
+When [Require admin approval for new sign-ups](settings/sign_up_restrictions.md#require-admin-approval-for-new-sign-ups) is enabled, any user that signs up for an account using the registration form is placed under a **Pending approval** state.
+
+A user pending approval is functionally identical to a [blocked](blocking_unblocking_users.md) user.
+
+A user pending approval:
+
+- Will not be able to sign in.
+- Cannot access Git repositories or the API.
+- Will not receive any notifications from GitLab.
+- Does not consume a [seat](../../subscriptions/self_managed/index.md#choose-the-number-of-users).
+
+## Approving a user
+
+A user that is pending approval can be approved from the Admin Area. To do this:
+
+1. Navigate to **Admin Area > Overview > Users**.
+1. Click on the **Pending approval** tab.
+1. Select a user.
+1. Under the **Account** tab, click **Approve user**.
+
+Approving a user:
+
+1. Activates their account.
+1. Changes the user's state to active and it consumes a
+[seat](../../subscriptions/self_managed/index.md#choose-the-number-of-users).
diff --git a/doc/user/admin_area/credentials_inventory.md b/doc/user/admin_area/credentials_inventory.md
index e66d5b81d0d..b17d0ab3dd5 100644
--- a/doc/user/admin_area/credentials_inventory.md
+++ b/doc/user/admin_area/credentials_inventory.md
@@ -11,7 +11,7 @@ type: howto
GitLab administrators are responsible for the overall security of their instance. To assist, GitLab provides a Credentials inventory to keep track of all the credentials that can be used to access their self-managed instance.
-Using Credentials inventory, you can see all the personal access tokens (PAT) and SSH keys that exist in your GitLab instance. In addition, you can [revoke them](#revoke-a-users-personal-access-token) and see:
+Using Credentials inventory, you can see all the personal access tokens (PAT) and SSH keys that exist in your GitLab instance. In addition, you can [revoke](#revoke-a-users-personal-access-token) and [delete](#delete-a-users-ssh-key) and see:
- Who they belong to.
- Their access scope.
@@ -27,7 +27,7 @@ The following is an example of the Credentials inventory page:
## Revoke a user's personal access token
-[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214811) in GitLab 13.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214811) in GitLab 13.4.
If you see a **Revoke** button, you can revoke that user's PAT. Whether you see a **Revoke** button depends on the token state, and if an expiration date has been set. For more information, see the following table:
@@ -39,3 +39,11 @@ If you see a **Revoke** button, you can revoke that user's PAT. Whether you see
| Expired | No | Yes | The administrator may revoke the PAT to prevent indefinite use |
| Revoked | Yes | No | Not applicable; token is already revoked |
| Revoked | No | No | Not applicable; token is already revoked |
+
+## Delete a user's SSH key
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225248) in GitLab 13.5.
+
+You can **Delete** a user's SSH key by navigating to the credentials inventory's SSH Keys tab.
+
+![Credentials inventory page - SSH keys](img/credentials_inventory_ssh_keys_v13_5.png)
diff --git a/doc/user/admin_area/img/credentials_inventory_ssh_keys_v13_5.png b/doc/user/admin_area/img/credentials_inventory_ssh_keys_v13_5.png
new file mode 100644
index 00000000000..f5edf513bbf
--- /dev/null
+++ b/doc/user/admin_area/img/credentials_inventory_ssh_keys_v13_5.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/email_confirmation_v12_7.png b/doc/user/admin_area/settings/img/email_confirmation_v12_7.png
deleted file mode 100644
index 6bcadb63b9a..00000000000
--- a/doc/user/admin_area/settings/img/email_confirmation_v12_7.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/sign_up_restrictions_v13_5.png b/doc/user/admin_area/settings/img/sign_up_restrictions_v13_5.png
new file mode 100644
index 00000000000..ebbfad77e69
--- /dev/null
+++ b/doc/user/admin_area/settings/img/sign_up_restrictions_v13_5.png
Binary files differ
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 80092102091..f57cf7c2045 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -7,6 +7,7 @@ type: reference
You can use sign-up restrictions to:
- Disable new sign-ups.
+- Require admin approval for new sign-ups.
- Require user email confirmation.
- Denylist or allowlist email addresses belonging to specific domains.
@@ -32,12 +33,20 @@ Alternatively, you could also consider setting up a
[allowlist](#allowlist-email-domains) or [denylist](#denylist-email-domains) on
email domains to prevent malicious users from creating accounts.
+## Require admin approval for new sign-ups
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4491) in GitLab 13.5.
+
+When this setting is enabled, any user visiting your GitLab domain and signing up for a new account will have to be explicitly [approved](../approving_users.md#approving-a-user) by an administrator before they can start using their account.
+
+![Require admin approval for new signups](img/sign_up_restrictions_v13_5.png)
+
## Require email confirmation
You can send confirmation emails during sign-up and require that users confirm
their email address before they are allowed to sign in.
-![Email confirmation](img/email_confirmation_v12_7.png)
+![Email confirmation](img/sign_up_restrictions_v13_5.png)
## Minimum password length limit
diff --git a/doc/user/img/gitlab_snippet_v13_5.png b/doc/user/img/gitlab_snippet_v13_5.png
index f824aa6bf4f..3fce1d25c3d 100644
--- a/doc/user/img/gitlab_snippet_v13_5.png
+++ b/doc/user/img/gitlab_snippet_v13_5.png
Binary files differ
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index 6a6562521fe..fa5bfc1cbe9 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -6,7 +6,7 @@ module API
namespace 'usage_data' do
before do
- not_found! unless Feature.enabled?(:usage_data_api)
+ not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
forbidden!('Invalid CSRF token is provided') unless verified_request?
end
diff --git a/lib/gitlab/cluster/puma_worker_killer_initializer.rb b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
index 92c799875b5..822012e0ed6 100644
--- a/lib/gitlab/cluster/puma_worker_killer_initializer.rb
+++ b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
@@ -5,8 +5,8 @@ module Gitlab
class PumaWorkerKillerInitializer
def self.start(
puma_options,
- puma_per_worker_max_memory_mb: 850,
- puma_master_max_memory_mb: 550,
+ puma_per_worker_max_memory_mb: 1024,
+ puma_master_max_memory_mb: 800,
additional_puma_dev_max_memory_mb: 200
)
require 'puma_worker_killer'
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index c17568efac6..ef8f338cae3 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -45,7 +45,7 @@ module Gitlab
# made globally available to the frontend
push_frontend_feature_flag(:webperf_experiment, default_enabled: false)
push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false)
- push_frontend_feature_flag(:usage_data_api, default_enabled: false)
+ push_frontend_feature_flag(:usage_data_api, default_enabled: true)
# Startup CSS feature is a special one as it can be enabled by means of cookies and params
gon.push({ features: { 'startupCss' => use_startup_css? } }, true)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c6f57c364dd..098222c277b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4467,6 +4467,15 @@ msgstr ""
msgid "Bulk request concurrency"
msgstr ""
+msgid "BulkImport|expected an associated Group but has an associated Project"
+msgstr ""
+
+msgid "BulkImport|expected an associated Project but has an associated Group"
+msgstr ""
+
+msgid "BulkImport|must be a group"
+msgstr ""
+
msgid "Burndown chart"
msgstr ""
@@ -14936,6 +14945,9 @@ msgstr ""
msgid "Keep divergent refs"
msgstr ""
+msgid "Keep editing"
+msgstr ""
+
msgid "Kerberos access denied"
msgstr ""
@@ -21353,6 +21365,9 @@ msgstr ""
msgid "Purchase more minutes"
msgstr ""
+msgid "Purchase more storage"
+msgstr ""
+
msgid "Push"
msgstr ""
@@ -29453,7 +29468,7 @@ msgstr ""
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
-msgid "When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by the admin before they can login. This setting is effective only if sign-ups are enabled."
+msgid "When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled."
msgstr ""
msgid "When enabled, any user visiting %{host} will be able to create an account."
diff --git a/package.json b/package.json
index f7d77934458..8851f0a60a0 100644
--- a/package.json
+++ b/package.json
@@ -105,6 +105,7 @@
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
+ "linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
"marked": "^0.3.12",
"mermaid": "^8.5.2",
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 95759d3b603..0fb5238a308 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -8,7 +8,7 @@ module QA
include Component::CiBadgeLink
view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
- element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern
+ element :pipeline_header
end
view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
@@ -35,7 +35,7 @@ module QA
end
def running?(wait: 0)
- within('.ci-header-container') do
+ within_element(:pipeline_header) do
page.has_content?('running', wait: wait)
end
end
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index cca453fd075..91bb4f5a0d3 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe RegistrationsController do
expect(response).to redirect_to(new_user_session_path(anchor: 'login-pane'))
expect(flash[:notice])
- .to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your administrator.')
+ .to eq('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator.')
end
context 'email confirmation' do
diff --git a/spec/factories/bulk_import.rb b/spec/factories/bulk_import.rb
new file mode 100644
index 00000000000..0231fe7cfef
--- /dev/null
+++ b/spec/factories/bulk_import.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :bulk_import, class: 'BulkImport' do
+ user
+ source_type { :gitlab }
+ end
+end
diff --git a/spec/factories/bulk_import/entities.rb b/spec/factories/bulk_import/entities.rb
new file mode 100644
index 00000000000..3bf6af92d00
--- /dev/null
+++ b/spec/factories/bulk_import/entities.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :bulk_import_entity, class: 'BulkImports::Entity' do
+ bulk_import
+
+ source_type { :group_entity }
+ sequence(:source_full_path) { |n| "source-path-#{n}" }
+
+ sequence(:destination_namespace) { |n| "destination-path-#{n}" }
+ destination_name { 'Imported Entity' }
+
+ trait(:group_entity) do
+ source_type { :group_entity }
+ end
+
+ trait(:project_entity) do
+ source_type { :project_entity }
+ end
+ end
+end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index c8c3a52d427..51826d867cd 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -178,7 +178,7 @@ RSpec.describe 'Pipeline', :js do
it 'pipeline header shows the user status and emoji' do
visit project_pipeline_path(project, pipeline)
- within '[data-testid="pipeline-header-content"]' do
+ within '[data-testid="ci-header-content"]' do
expect(page).to have_selector("[data-testid='#{status.message}']")
expect(page).to have_selector("[data-name='#{status.emoji}']")
end
diff --git a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
index e7139ceaa93..d7df2ff1563 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -114,4 +114,16 @@ describe('boards sidebar remove issue', () => {
expect(wrapper.emitted().open.length).toBe(1);
});
+
+ it('does not emits events when collapsing with false `emitEvent`', async () => {
+ createComponent({ canUpdate: true });
+
+ findEditButton().vm.$emit('click');
+
+ await wrapper.vm.$nextTick();
+
+ wrapper.vm.collapse({ emitEvent: false });
+
+ expect(wrapper.emitted().close).toBeUndefined();
+ });
});
diff --git a/spec/frontend/jobs/components/log/line_spec.js b/spec/frontend/jobs/components/log/line_spec.js
index c2412a807c3..1a30921fece 100644
--- a/spec/frontend/jobs/components/log/line_spec.js
+++ b/spec/frontend/jobs/components/log/line_spec.js
@@ -2,21 +2,25 @@ import { shallowMount } from '@vue/test-utils';
import Line from '~/jobs/components/log/line.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
+const httpUrl = 'http://example.com';
+const httpsUrl = 'https://example.com';
+
+const mockProps = ({ text = 'Running with gitlab-runner 12.1.0 (de7731dd)' } = {}) => ({
+ line: {
+ content: [
+ {
+ text,
+ style: 'term-fg-l-green',
+ },
+ ],
+ lineNumber: 0,
+ },
+ path: '/jashkenas/underscore/-/jobs/335',
+});
+
describe('Job Log Line', () => {
let wrapper;
-
- const data = {
- line: {
- content: [
- {
- text: 'Running with gitlab-runner 12.1.0 (de7731dd)',
- style: 'term-fg-l-green',
- },
- ],
- lineNumber: 0,
- },
- path: '/jashkenas/underscore/-/jobs/335',
- };
+ let data;
const createComponent = (props = {}) => {
wrapper = shallowMount(Line, {
@@ -27,6 +31,7 @@ describe('Job Log Line', () => {
};
beforeEach(() => {
+ data = mockProps();
createComponent(data);
});
@@ -45,4 +50,38 @@ describe('Job Log Line', () => {
it('renders the provided style as a class attribute', () => {
expect(wrapper.find('span').classes()).toContain(data.line.content[0].style);
});
+
+ describe('when the line contains a link', () => {
+ const findLink = () => wrapper.find('span a');
+
+ it('renders an http link', () => {
+ createComponent(mockProps({ text: httpUrl }));
+
+ expect(findLink().text()).toBe(httpUrl);
+ expect(findLink().attributes().href).toEqual(httpUrl);
+ });
+
+ it('renders an https link', () => {
+ createComponent(mockProps({ text: httpsUrl }));
+
+ expect(findLink().text()).toBe(httpsUrl);
+ expect(findLink().attributes().href).toEqual(httpsUrl);
+ });
+
+ it('renders a link with rel nofollow and noopener', () => {
+ createComponent(mockProps({ text: httpsUrl }));
+
+ expect(findLink().attributes().rel).toBe('nofollow noopener');
+ });
+
+ test.each`
+ type | text
+ ${'ftp'} | ${'ftp://example.com/file'}
+ ${'email'} | ${'email@example.com'}
+ ${'no scheme'} | ${'example.com/page'}
+ `('does not render a $type link', ({ text }) => {
+ createComponent(mockProps({ text }));
+ expect(findLink().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
index 0e157f8efdf..191f91be076 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
@@ -1,14 +1,21 @@
import { shallowMount } from '@vue/test-utils';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
import { mergeRequestMeta } from '../mock_data';
-describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
+describe('~/static_site_editor/components/edit_meta_controls.vue', () => {
+ useLocalStorageSpy();
+
let wrapper;
+ let mockSelect;
+ let mockGlFormInputTitleInstance;
const { title, description } = mergeRequestMeta;
+ const newTitle = 'New title';
+ const newDescription = 'New description';
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(EditMetaControls, {
@@ -20,11 +27,18 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
});
};
+ const buildMocks = () => {
+ mockSelect = jest.fn();
+ mockGlFormInputTitleInstance = { $el: { select: mockSelect } };
+ wrapper.vm.$refs.title = mockGlFormInputTitleInstance;
+ };
+
const findGlFormInputTitle = () => wrapper.find(GlFormInput);
const findGlFormTextAreaDescription = () => wrapper.find(GlFormTextarea);
beforeEach(() => {
buildWrapper();
+ buildMocks();
return wrapper.vm.$nextTick();
});
@@ -50,23 +64,36 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
expect(findGlFormTextAreaDescription().attributes().value).toBe(description);
});
- it('emits updated settings when title input updates', () => {
- const newTitle = 'New title';
+ it('calls select on the title input when mounted', () => {
+ expect(mockGlFormInputTitleInstance.$el.select).toHaveBeenCalled();
+ });
- findGlFormInputTitle().vm.$emit('input', newTitle);
+ describe('when inputs change', () => {
+ const storageKey = 'sse-merge-request-meta-local-storage-editable';
- const newSettings = { description, title: newTitle };
+ afterEach(() => {
+ localStorage.removeItem(storageKey);
+ });
- expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
- });
+ it.each`
+ findFn | key | value
+ ${findGlFormInputTitle} | ${'title'} | ${newTitle}
+ ${findGlFormTextAreaDescription} | ${'description'} | ${newDescription}
+ `('emits updated settings when $findFn input updates', ({ key, value, findFn }) => {
+ findFn().vm.$emit('input', value);
+
+ const newSettings = { ...mergeRequestMeta, [key]: value };
- it('emits updated settings when description textarea updates', () => {
- const newDescription = 'New description';
+ expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
+ });
- findGlFormTextAreaDescription().vm.$emit('input', newDescription);
+ it('should remember the input changes', () => {
+ findGlFormInputTitle().vm.$emit('input', newTitle);
+ findGlFormTextAreaDescription().vm.$emit('input', newDescription);
- const newSettings = { description: newDescription, title };
+ const newSettings = { title: newTitle, description: newDescription };
- expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
+ expect(localStorage.setItem).toHaveBeenCalledWith(storageKey, JSON.stringify(newSettings));
+ });
});
});
diff --git a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
index 3fbd3542ed3..7a5685033f3 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
@@ -9,6 +9,8 @@ import { sourcePath, mergeRequestMeta } from '../mock_data';
describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
let wrapper;
+ let resetCachedEditable;
+ let mockEditMetaControlsInstance;
const { title, description } = mergeRequestMeta;
const buildWrapper = (propsData = {}) => {
@@ -20,11 +22,18 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
});
};
+ const buildMocks = () => {
+ resetCachedEditable = jest.fn();
+ mockEditMetaControlsInstance = { resetCachedEditable };
+ wrapper.vm.$refs.editMetaControls = mockEditMetaControlsInstance;
+ };
+
const findGlModal = () => wrapper.find(GlModal);
const findEditMetaControls = () => wrapper.find(EditMetaControls);
beforeEach(() => {
buildWrapper();
+ buildMocks();
return wrapper.vm.$nextTick();
});
@@ -59,6 +68,11 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
expect(wrapper.emitted('primary')).toEqual([[mergeRequestMeta]]);
});
+ it('calls resetCachedEditable on EditMetaControls when primary emits', () => {
+ findGlModal().vm.$emit('primary', mergeRequestMeta);
+ expect(mockEditMetaControlsInstance.resetCachedEditable).toHaveBeenCalled();
+ });
+
it('emits the hide event', () => {
findGlModal().vm.$emit('hide');
expect(wrapper.emitted('hide')).toEqual([[]]);
diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 65255968bc7..08fc822577e 100644
--- a/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -80,7 +80,7 @@ describe('collapsedGroupedDatePicker', () => {
it('should have tooltip as `Start and due date`', () => {
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
- expect(icons[0].dataset.originalTitle).toBe('Start and due date');
+ expect(icons[0].title).toBe('Start and due date');
});
});
});
diff --git a/spec/models/bulk_import_spec.rb b/spec/models/bulk_import_spec.rb
new file mode 100644
index 00000000000..1a7e1ed8119
--- /dev/null
+++ b/spec/models/bulk_import_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImport, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:user).required }
+ it { is_expected.to have_one(:configuration) }
+ it { is_expected.to have_many(:entities) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:source_type) }
+ it { is_expected.to validate_presence_of(:status) }
+
+ it { is_expected.to define_enum_for(:source_type).with_values(%i[gitlab]) }
+ end
+end
diff --git a/spec/models/bulk_imports/configuration_spec.rb b/spec/models/bulk_imports/configuration_spec.rb
new file mode 100644
index 00000000000..1cbfef631ac
--- /dev/null
+++ b/spec/models/bulk_imports/configuration_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Configuration, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:bulk_import).required }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_length_of(:url).is_at_most(255) }
+ it { is_expected.to validate_length_of(:access_token).is_at_most(255) }
+
+ it { is_expected.to validate_presence_of(:url) }
+ it { is_expected.to validate_presence_of(:access_token) }
+ end
+end
diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb
new file mode 100644
index 00000000000..ad6e3ec6f30
--- /dev/null
+++ b/spec/models/bulk_imports/entity_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::Entity, type: :model do
+ describe 'associations' do
+ it { is_expected.to belong_to(:bulk_import).required }
+ it { is_expected.to belong_to(:parent) }
+ it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:source_type) }
+ it { is_expected.to validate_presence_of(:source_full_path) }
+ it { is_expected.to validate_presence_of(:destination_name) }
+ it { is_expected.to validate_presence_of(:destination_namespace) }
+
+ it { is_expected.to define_enum_for(:source_type).with_values(%i[group_entity project_entity]) }
+
+ context 'when associated with a group and project' do
+ it 'is invalid' do
+ entity = build(:bulk_import_entity, group: build(:group), project: build(:project))
+
+ expect(entity).not_to be_valid
+ expect(entity.errors).to include(:project, :group)
+ end
+ end
+
+ context 'when not associated with a group or project' do
+ it 'is valid' do
+ entity = build(:bulk_import_entity, group: nil, project: nil)
+
+ expect(entity).to be_valid
+ end
+ end
+
+ context 'when associated with a group and no project' do
+ it 'is valid as a group_entity' do
+ entity = build(:bulk_import_entity, :group_entity, group: build(:group), project: nil)
+
+ expect(entity).to be_valid
+ end
+
+ it 'is invalid as a project_entity' do
+ entity = build(:bulk_import_entity, :project_entity, group: build(:group), project: nil)
+
+ expect(entity).not_to be_valid
+ expect(entity.errors).to include(:group)
+ end
+ end
+
+ context 'when associated with a project and no group' do
+ it 'is valid' do
+ entity = build(:bulk_import_entity, :project_entity, group: nil, project: build(:project))
+
+ expect(entity).to be_valid
+ end
+
+ it 'is invalid as a project_entity' do
+ entity = build(:bulk_import_entity, :group_entity, group: nil, project: build(:project))
+
+ expect(entity).not_to be_valid
+ expect(entity.errors).to include(:project)
+ end
+ end
+
+ context 'when the parent is a group import' do
+ it 'is valid' do
+ entity = build(:bulk_import_entity, parent: build(:bulk_import_entity, :group_entity))
+
+ expect(entity).to be_valid
+ end
+ end
+
+ context 'when the parent is a project import' do
+ it 'is invalid' do
+ entity = build(:bulk_import_entity, parent: build(:bulk_import_entity, :project_entity))
+
+ expect(entity).not_to be_valid
+ expect(entity.errors).to include(:parent)
+ end
+ end
+ end
+end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 80bd47ec102..3e855584c38 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -122,8 +122,8 @@ RSpec.describe Deployment do
deployment.run!
end
- it 'executes Deployments::ForwardDeploymentWorker asynchronously' do
- expect(Deployments::ForwardDeploymentWorker)
+ it 'executes Deployments::DropOlderDeploymentsWorker asynchronously' do
+ expect(Deployments::DropOlderDeploymentsWorker)
.to receive(:perform_async).once.with(deployment.id)
deployment.run!
diff --git a/spec/workers/deployments/drop_older_deployments_worker_spec.rb b/spec/workers/deployments/drop_older_deployments_worker_spec.rb
new file mode 100644
index 00000000000..0cf524ca16f
--- /dev/null
+++ b/spec/workers/deployments/drop_older_deployments_worker_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Deployments::DropOlderDeploymentsWorker do
+ subject { described_class.new.perform(deployment&.id) }
+
+ describe '#perform' do
+ let(:deployment) { create(:deployment, :success) }
+
+ it 'executes Deployments::OlderDeploymentsDropService' do
+ expect(Deployments::OlderDeploymentsDropService)
+ .to receive(:new).with(deployment.id).and_call_original
+
+ subject
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index d0251a2e627..192a2a35e7c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7550,6 +7550,11 @@ linkify-it@^2.0.0:
dependencies:
uc.micro "^1.0.1"
+linkifyjs@^2.1.9:
+ version "2.1.9"
+ resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.9.tgz#af06e45a2866ff06c4766582590d098a4d584702"
+ integrity sha512-74ivurkK6WHvHFozVaGtQWV38FzBwSTGNmJolEgFp7QgR2bl6ArUWlvT4GcHKbPe1z3nWYi+VUdDZk16zDOVug==
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"