summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-10-04 12:06:14 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-10-04 12:06:14 +0000
commit0d46bf06388d485824bc2f1e736b92b2a8a397e4 (patch)
tree626a835841722463da4def7905b95e874eb77578
parent1f1bdf54e1974f89f3a6ba734ec2c42552e90639 (diff)
downloadgitlab-ce-0d46bf06388d485824bc2f1e736b92b2a8a397e4.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/notifications.gitlab-ci.yml8
-rw-r--r--app/assets/javascripts/blob/template_selector.js11
-rw-r--r--app/assets/javascripts/gl_dropdown.js1
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js4
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue24
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js56
-rw-r--r--app/assets/javascripts/templates/issuable_template_selectors.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue2
-rw-r--r--app/assets/stylesheets/components/release_block.scss3
-rw-r--r--app/assets/stylesheets/framework/common.scss22
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/atomic_internal_id.rb74
-rw-r--r--app/views/shared/form_elements/_apply_template_warning.html.haml14
-rw-r--r--app/views/shared/issuable/_form.html.haml1
-rw-r--r--changelogs/unreleased/16188-warn-before-applying-issue-templates.yml5
-rw-r--r--changelogs/unreleased/nfriend-allow-release-page-anchor-scrolling.yml5
-rw-r--r--db/migrate/20190919183411_add_index_packages_on_name_trigram_to_packages_packages.rb18
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/custom_hooks.md7
-rw-r--r--doc/development/database_review.md5
-rw-r--r--doc/development/import_export.md2
-rw-r--r--doc/development/testing_guide/index.md4
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/update/upgrading_from_source.md2
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md2
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md2
-rw-r--r--doc/user/application_security/img/dismissed_info.pngbin7847 -> 0 bytes
-rw-r--r--doc/user/application_security/img/dismissed_info_v12_3.pngbin0 -> 35439 bytes
-rw-r--r--doc/user/application_security/index.md8
-rw-r--r--lib/gitlab/import_export/importer.rb16
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb51
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/projects/issuable_templates_spec.rb30
-rw-r--r--spec/frontend/releases/components/release_block_spec.js69
-rw-r--r--spec/javascripts/vue_mr_widget/components/deployment_spec.js6
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb22
-rw-r--r--spec/models/concerns/atomic_internal_id_spec.rb64
38 files changed, 430 insertions, 126 deletions
diff --git a/.gitlab/ci/notifications.gitlab-ci.yml b/.gitlab/ci/notifications.gitlab-ci.yml
index 21f7312063a..8e00ba022d0 100644
--- a/.gitlab/ci/notifications.gitlab-ci.yml
+++ b/.gitlab/ci/notifications.gitlab-ci.yml
@@ -10,8 +10,8 @@ schedule:package-and-qa:notify-success:
extends:
- .only-canonical-schedules
- .notify
- before_script:
- - export COMMIT_NOTES_URL="https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
+ variables:
+ COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
script:
- 'scripts/notify-slack qa-master ":tada: Scheduled QA against master passed! :tada: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_passing'
needs: ["schedule:package-and-qa"]
@@ -21,8 +21,8 @@ schedule:package-and-qa:notify-failure:
extends:
- .only-canonical-schedules
- .notify
- before_script:
- - export COMMIT_NOTES_URL="https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
+ variables:
+ COMMIT_NOTES_URL: "https://$CI_SERVER_HOST/$CI_PROJECT_PATH/commit/$CI_COMMIT_SHA#notes-list"
script:
- 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL. For downstream pipelines, see $COMMIT_NOTES_URL" ci_failing'
needs: ["schedule:package-and-qa"]
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 02216e4e93d..6cfe354d277 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -27,11 +27,16 @@ export default class TemplateSelector {
search: {
fields: ['name'],
},
- clicked: options => this.fetchFileTemplate(options),
+ clicked: options => this.onDropdownClicked(options),
text: item => item.name,
});
}
+ // Subclasses can override this method to conditionally prevent fetching file templates
+ onDropdownClicked(options) {
+ this.fetchFileTemplate(options);
+ }
+
initAutosizeUpdateEvent() {
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
@@ -81,6 +86,10 @@ export default class TemplateSelector {
}
}
+ getEditorContent() {
+ return this.editor.getValue();
+ }
+
startLoadingSpinner() {
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 437c4941fda..4e1b4f2652c 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -717,6 +717,7 @@ GitLabDropdown = (function() {
selectedObject = this.renderedData[groupName][selectedIndex];
} else {
selectedIndex = el.closest('li').index();
+ this.selectedIndex = selectedIndex;
selectedObject = this.renderedData[selectedIndex];
}
}
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 2205a7bafe3..96e47187fed 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -15,7 +15,9 @@ export default () => {
new IssuableForm($('.issue-form'));
new LabelsSelect();
new MilestoneSelect();
- new IssuableTemplateSelectors();
+ new IssuableTemplateSelectors({
+ warnTemplateOverride: true,
+ });
initSuggestions();
};
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 8f0dc8554e2..e51ab79a51d 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -16,5 +16,7 @@ export default () => {
new IssuableForm($('.merge-request-form'));
new LabelsSelect();
new MilestoneSelect();
- new IssuableTemplateSelectors();
+ new IssuableTemplateSelectors({
+ warnTemplateOverride: true,
+ });
};
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 7b6bd9913a8..921ada91544 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -6,6 +6,9 @@ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { __, n__, sprintf } from '../../locale';
+import { slugify } from '~/lib/utils/text_utility';
+import { getLocationHash } from '~/lib/utils/url_utility';
+import { scrollToElement } from '~/lib/utils/common_utils';
export default {
name: 'ReleaseBlock',
@@ -26,7 +29,15 @@ export default {
default: () => ({}),
},
},
+ data() {
+ return {
+ isHighlighted: false,
+ };
+ },
computed: {
+ id() {
+ return slugify(this.release.tag_name);
+ },
releasedTimeAgo() {
return sprintf(__('released %{time}'), {
time: this.timeFormated(this.release.released_at),
@@ -62,10 +73,21 @@ export default {
return n__('Milestone', 'Milestones', this.release.milestones.length);
},
},
+ mounted() {
+ const hash = getLocationHash();
+ if (hash && slugify(hash) === this.id) {
+ this.isHighlighted = true;
+ setTimeout(() => {
+ this.isHighlighted = false;
+ }, 2000);
+
+ scrollToElement(this.$el);
+ }
+ },
};
</script>
<template>
- <div :id="release.tag_name" class="card">
+ <div :id="id" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block">
<div class="card-body">
<h2 class="card-title mt-0">
{{ release.name }}
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 78609ce0610..78a1c4fa8a8 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -8,10 +8,13 @@ import { __ } from '~/locale';
export default class IssuableTemplateSelector extends TemplateSelector {
constructor(...args) {
super(...args);
+
this.projectPath = this.dropdown.data('projectPath');
this.namespacePath = this.dropdown.data('namespacePath');
this.issuableType = this.$dropdownContainer.data('issuableType');
this.titleInput = $(`#${this.issuableType}_title`);
+ this.templateWarningEl = $('.js-issuable-template-warning');
+ this.warnTemplateOverride = args[0].warnTemplateOverride;
const initialQuery = {
name: this.dropdown.data('selected'),
@@ -24,14 +27,61 @@ export default class IssuableTemplateSelector extends TemplateSelector {
});
$('.no-template', this.dropdown.parent()).on('click', () => {
- this.currentTemplate.content = '';
- this.setInputValueToTemplateContent();
- $('.dropdown-toggle-text', this.dropdown).text(__('Choose a template'));
+ this.reset();
+ });
+
+ this.templateWarningEl.find('.js-close-btn').on('click', () => {
+ if (this.previousSelectedIndex) {
+ this.dropdown.data('glDropdown').selectRowAtIndex(this.previousSelectedIndex);
+ } else {
+ this.reset();
+ }
+
+ this.templateWarningEl.addClass('hidden');
+ });
+
+ this.templateWarningEl.find('.js-override-template').on('click', () => {
+ this.requestFile(this.overridingTemplate);
+ this.setSelectedIndex();
+
+ this.templateWarningEl.addClass('hidden');
+ this.overridingTemplate = null;
});
}
+ reset() {
+ if (this.currentTemplate) {
+ this.currentTemplate.content = '';
+ }
+
+ this.setInputValueToTemplateContent();
+ $('.dropdown-toggle-text', this.dropdown).text(__('Choose a template'));
+ this.previousSelectedIndex = null;
+ }
+
+ setSelectedIndex() {
+ this.previousSelectedIndex = this.dropdown.data('glDropdown').selectedIndex;
+ }
+
+ onDropdownClicked(query) {
+ const content = this.getEditorContent();
+ const isContentUnchanged =
+ content === '' || (this.currentTemplate && content === this.currentTemplate.content);
+
+ if (!this.warnTemplateOverride || isContentUnchanged) {
+ super.onDropdownClicked(query);
+ this.setSelectedIndex();
+
+ return;
+ }
+
+ this.overridingTemplate = query.selectedObj;
+ this.templateWarningEl.removeClass('hidden');
+ }
+
requestFile(query) {
this.startLoadingSpinner();
+
Api.issueTemplate(
this.namespacePath,
this.projectPath,
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js b/app/assets/javascripts/templates/issuable_template_selectors.js
index 50e58ec5c46..443b3084113 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import IssuableTemplateSelector from './issuable_template_selector';
export default class IssuableTemplateSelectors {
- constructor({ $dropdowns, editor } = {}) {
+ constructor({ $dropdowns, editor, warnTemplateOverride } = {}) {
this.$dropdowns = $dropdowns || $('.js-issuable-selector');
this.editor = editor || this.initEditor();
@@ -16,6 +16,7 @@ export default class IssuableTemplateSelectors {
wrapper: $dropdown.closest('.js-issuable-selector-wrap'),
dropdown: $dropdown,
editor: this.editor,
+ warnTemplateOverride,
});
});
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index bb6921225c2..1873e09c370 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -211,7 +211,7 @@ export default {
<template v-else>
<review-app-link
:link="deploymentExternalUrl"
- css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
</template>
<visual-review-app-link
diff --git a/app/assets/stylesheets/components/release_block.scss b/app/assets/stylesheets/components/release_block.scss
new file mode 100644
index 00000000000..7e82d0960d7
--- /dev/null
+++ b/app/assets/stylesheets/components/release_block.scss
@@ -0,0 +1,3 @@
+.release-block {
+ transition: background-color 1s linear;
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 4cd6763e7d7..922051ab0e9 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -55,6 +55,10 @@
background-color: $gray-light;
}
+.bg-line-target-blue {
+ background: $line-target-blue;
+}
+
.text-break-word {
word-break: break-all;
}
@@ -210,18 +214,26 @@ li.note {
@mixin message($background-color, $border-color, $text-color) {
border-left: 4px solid $border-color;
color: $text-color;
- padding: 10px;
- margin-bottom: 10px;
- background: $background-color;
- padding-left: 20px;
+ padding: $gl-padding $gl-padding-24;
+ margin-bottom: $gl-padding-12;
+ background-color: $background-color;
&.centered {
text-align: center;
}
+
+ .close {
+ svg {
+ width: $gl-font-size-large;
+ height: $gl-font-size-large;
+ }
+
+ color: inherit;
+ }
}
.warning_message {
- @include message($orange-100, $orange-200, $orange-700);
+ @include message($orange-100, $orange-200, $orange-800);
}
.danger_message {
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 7fa290610aa..aed95b4601b 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -25,7 +25,7 @@ module Ci
belongs_to :merge_request, class_name: 'MergeRequest'
belongs_to :external_pull_request
- has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
+ has_internal_id :iid, scope: :project, presence: false, ensure_if: -> { !importing? }, init: ->(s) do
s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count
end
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 95de11a72bf..b510129b35d 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -27,53 +27,73 @@ module AtomicInternalId
extend ActiveSupport::Concern
class_methods do
- def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
+ def has_internal_id(column, scope:, init:, ensure_if: nil, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example).
raise "has_internal_id requires a init block, none given." unless init
+ raise "has_internal_id needs to be defined on association." unless self.reflect_on_association(scope)
- before_validation :"ensure_#{scope}_#{column}!", on: :create
+ before_validation :"track_#{scope}_#{column}!", on: :create
+ before_validation :"ensure_#{scope}_#{column}!", on: :create, if: ensure_if
validates column, presence: presence
define_method("ensure_#{scope}_#{column}!") do
- scope_value = association(scope).reader
+ scope_value = internal_id_read_scope(scope)
value = read_attribute(column)
-
return value unless scope_value
- scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
- usage = self.class.table_name.to_sym
-
- if value.present? && (@iid_needs_tracking || Feature.enabled?(:iid_always_track, default_enabled: true))
- # The value was set externally, e.g. by the user
- # We update the InternalId record to keep track of the greatest value.
- InternalId.track_greatest(self, scope_attrs, usage, value, init)
-
- @iid_needs_tracking = false
- elsif !value.present?
+ if value.nil?
# We don't have a value yet and use a InternalId record to generate
# the next value.
- value = InternalId.generate_next(self, scope_attrs, usage, init)
+ value = InternalId.generate_next(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ init)
write_attribute(column, value)
end
value
end
+ define_method("track_#{scope}_#{column}!") do
+ iid_always_track = Feature.enabled?(:iid_always_track, default_enabled: true)
+ return unless @internal_id_needs_tracking || iid_always_track
+
+ @internal_id_needs_tracking = false
+
+ scope_value = internal_id_read_scope(scope)
+ value = read_attribute(column)
+ return unless scope_value
+
+ if value.present?
+ # The value was set externally, e.g. by the user
+ # We update the InternalId record to keep track of the greatest value.
+ InternalId.track_greatest(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ value,
+ init)
+ end
+ end
+
define_method("#{column}=") do |value|
super(value).tap do |v|
# Indicate the iid was set from externally
- @iid_needs_tracking = true
+ @internal_id_needs_tracking = true
end
end
define_method("reset_#{scope}_#{column}") do
if value = read_attribute(column)
- scope_value = association(scope).reader
- scope_attrs = { scope_value.class.table_name.singularize.to_sym => scope_value }
- usage = self.class.table_name.to_sym
+ did_reset = InternalId.reset(
+ self,
+ internal_id_scope_attrs(scope),
+ internal_id_scope_usage,
+ value)
- if InternalId.reset(self, scope_attrs, usage, value)
+ if did_reset
write_attribute(column, nil)
end
end
@@ -82,4 +102,18 @@ module AtomicInternalId
end
end
end
+
+ def internal_id_scope_attrs(scope)
+ scope_value = internal_id_read_scope(scope)
+
+ { scope_value.class.table_name.singularize.to_sym => scope_value } if scope_value
+ end
+
+ def internal_id_scope_usage
+ self.class.table_name.to_sym
+ end
+
+ def internal_id_read_scope(scope)
+ association(scope).reader
+ end
end
diff --git a/app/views/shared/form_elements/_apply_template_warning.html.haml b/app/views/shared/form_elements/_apply_template_warning.html.haml
new file mode 100644
index 00000000000..9027264d221
--- /dev/null
+++ b/app/views/shared/form_elements/_apply_template_warning.html.haml
@@ -0,0 +1,14 @@
+.form-group.row.js-template-warning.mb-0.hidden.js-issuable-template-warning
+ .offset-sm-2.col-sm-10
+
+ .warning_message.mb-0{ role: 'alert' }
+ %btn.js-close-btn.close{ type: "button", "aria-hidden": true, "aria-label": _("Close") }
+ = sprite_icon("close")
+
+ %p
+ = _("Applying a template will replace the existing issue description. Any changes you have made will be lost.")
+
+ %button.js-override-template.btn.btn-warning.mr-2{ type: 'button' }
+ = _("Apply template")
+ %button.js-cancel-btn.btn.btn-inverted{ type: 'button' }
+ = _("Cancel")
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 04a70e406ca..5e2b5f95ee3 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -19,6 +19,7 @@
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
#js-suggestions{ data: { project_path: @project.full_path } }
+= render 'shared/form_elements/apply_template_warning'
= render 'shared/form_elements/description', model: issuable, form: form, project: project
- if issuable.respond_to?(:confidential)
diff --git a/changelogs/unreleased/16188-warn-before-applying-issue-templates.yml b/changelogs/unreleased/16188-warn-before-applying-issue-templates.yml
new file mode 100644
index 00000000000..c6cc6cb0992
--- /dev/null
+++ b/changelogs/unreleased/16188-warn-before-applying-issue-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Warn before applying issue templates
+merge_request: 16865
+author:
+type: changed
diff --git a/changelogs/unreleased/nfriend-allow-release-page-anchor-scrolling.yml b/changelogs/unreleased/nfriend-allow-release-page-anchor-scrolling.yml
new file mode 100644
index 00000000000..7b30852eba4
--- /dev/null
+++ b/changelogs/unreleased/nfriend-allow-release-page-anchor-scrolling.yml
@@ -0,0 +1,5 @@
+---
+title: Allow releases to be targeted by URL anchor links on the Releases page
+merge_request: 17150
+author:
+type: added
diff --git a/db/migrate/20190919183411_add_index_packages_on_name_trigram_to_packages_packages.rb b/db/migrate/20190919183411_add_index_packages_on_name_trigram_to_packages_packages.rb
new file mode 100644
index 00000000000..d359350a891
--- /dev/null
+++ b/db/migrate/20190919183411_add_index_packages_on_name_trigram_to_packages_packages.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddIndexPackagesOnNameTrigramToPackagesPackages < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_packages_packages_on_name_trigram'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :packages_packages, :name, name: INDEX_NAME, using: :gin, opclass: { name: :gin_trgm_ops }
+ end
+
+ def down
+ remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4ca75ece3f9..b2caca12ce4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -2556,6 +2556,7 @@ ActiveRecord::Schema.define(version: 2019_09_27_074328) do
t.string "name", null: false
t.string "version"
t.integer "package_type", limit: 2, null: false
+ t.index ["name"], name: "index_packages_packages_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["project_id"], name: "index_packages_packages_on_project_id"
end
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 3e714b446af..0702e0aa141 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -48,10 +48,9 @@ as appropriate.
## Set a global Git hook for all repositories
To create a Git hook that applies to all of your repositories in
-your instance, set a global Git hook. Since all the repositories' `hooks`
-directories are symlinked to GitLab Shell's `hooks` directory, adding any hook
-to the GitLab Shell `hooks` directory will also apply it to all repositories. Follow
-the steps below to properly set up a custom hook for all repositories:
+your instance, set a global Git hook. Since GitLab will look inside the GitLab Shell
+`hooks` directory for global hooks, adding any hook there will apply it to all repositories.
+Follow the steps below to properly set up a custom hook for all repositories:
1. On the GitLab server, navigate to the configured custom hook directory. The
default is in the GitLab Shell directory. The GitLab Shell `hook` directory
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 603c6290311..1ff23e2935e 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -78,7 +78,8 @@ and details for a database reviewer:
- Format any queries with a SQL query formatter, for example with [sqlformat.darold.net](http://sqlformat.darold.net).
- Consider providing query plans via a link to [explain.depesz.com](https://explain.depesz.com) or another tool instead of textual form.
- For query changes, it is best to provide the SQL query along with a plan *before* and *after* the change. This helps to spot differences quickly.
-- When providing query plans, make sure to use good parameter values, so that the query executed is a good example and also hits enough data. Usually, the `gitlab-org` namespace (`namespace_id = 9970`) and the `gitlab-org/gitlab-foss` project (`project_id = 13083`) provides enough data to serve as a good example.
+- When providing query plans, make sure to use good parameter values, so that the query executed is a good example and also hits enough data.
+ - Usually, the `gitlab-org` namespace (`namespace_id = 9970`) and the `gitlab-org/gitlab-foss` (`project_id = 13083`) or the `gitlab-org/gitlab` (`project_id = 278964`) projects provide enough data to serve as a good example.
### How to review for database
@@ -121,7 +122,7 @@ and details for a database reviewer:
pipeline](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd)
in order to establish a proper testing environment.
-### Timing guidelines for migrations
+### Timing guidelines for migrations
In general, migrations for a single deploy shouldn't take longer than
1 hour for GitLab.com. The following guidelines are not hard rules, they were
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index 470bad1777e..71f5ce18aae 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -312,7 +312,7 @@ module Gitlab
class Importer
def execute
if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
- project_tree.restored_project
+ project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index d9d3185a473..25da72b6b6a 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -60,6 +60,10 @@ Everything you should know about how to test Rake tasks.
Everything you should know about how to run end-to-end tests using
[GitLab QA][gitlab-qa] testing framework.
+## [Migrations tests](testing_migrations_guide.md)
+
+Everything you should know about how to test migrations.
+
[Return to Development documentation](../README.md)
[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 73677b019eb..7314e34666d 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -87,7 +87,7 @@ cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags
sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) -b v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
-sudo -u git -H sh -c 'if [ -x bin/compile ]; then bin/compile; fi'
+sudo -u git -H make build
```
### 7. Update GitLab Pages to the corresponding version (skip if not using pages)
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index d8c06f372b6..662701dbb56 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -193,7 +193,7 @@ cd /home/git/gitlab-shell
sudo -u git -H git fetch --all --tags --prune
sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
-sudo -u git -H bin/compile
+sudo -u git -H make build
```
### 9. Update GitLab Workhorse
diff --git a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
index 3a6f3a8c20e..29a3591184b 100644
--- a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -7,7 +7,7 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/30829) in GitLab 12.2.
This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
-It can be modified in **Admin Area > Network > Performance Optimization**.
+It can be modified in **Admin Area > Settings > Network > Performance Optimization**.
For example, requests over `300` per minute to `https://gitlab.com/gitlab-org/gitlab-foss/raw/master/app/controllers/application_controller.rb` will be blocked. Access to the raw file will be released after 1 minute.
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
index b9d93bf3671..5d49d88d254 100644
--- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -8,7 +8,7 @@ Rate limiting is a common technique used to improve the security and durability
of a web application. For more details, see
[Rate limits](../../../security/rate_limits.md).
-The following limits can be enforced in **Admin Area > Network > User and
+The following limits can be enforced in **Admin Area > Settings > Network > User and
IP rate limits**:
- Unauthenticated requests
diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png
deleted file mode 100644
index b4470b664d2..00000000000
--- a/doc/user/application_security/img/dismissed_info.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/img/dismissed_info_v12_3.png b/doc/user/application_security/img/dismissed_info_v12_3.png
new file mode 100644
index 00000000000..92037493eaa
--- /dev/null
+++ b/doc/user/application_security/img/dismissed_info_v12_3.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 28375510956..e9f5898950e 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -87,10 +87,12 @@ If you wish to undo this dismissal, you can click the **Undo dismiss** button.
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
When dismissing a vulnerability, it's often helpful to provide a reason for doing so.
-If you press the comment button next to **Dismiss vulnerability** in the modal, a text box will appear, allowing you to add a comment with your dismissal.
-This comment can not currently be edited or removed, but [future versions](https://gitlab.com/gitlab-org/gitlab/issues/11721) will add this functionality.
+If you press the comment button next to **Dismiss vulnerability** in the modal,
+a text box will appear, allowing you to add a comment with your dismissal.
+Once added, you can edit it or delete it. This allows you to add and update
+context for a vulnerability as you learn more over time.
-![Dismissed vulnerability comment](img/dismissed_info.png)
+![Dismissed vulnerability comment](img/dismissed_info_v12_3.png)
### Creating an issue for a vulnerability
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index f061a1916da..62cf6c86906 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -19,7 +19,7 @@ module Gitlab
def execute
if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
- project_tree.restored_project
+ project
else
raise Projects::ImportService::Error.new(shared.errors.to_sentence)
end
@@ -55,32 +55,32 @@ module Gitlab
end
def avatar_restorer
- Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: shared)
+ Gitlab::ImportExport::AvatarRestorer.new(project: project, shared: shared)
end
def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: shared,
- project: project_tree.restored_project)
+ project: project)
end
def wiki_restorer
Gitlab::ImportExport::WikiRestorer.new(path_to_bundle: wiki_repo_path,
shared: shared,
- project: ProjectWiki.new(project_tree.restored_project),
+ project: ProjectWiki.new(project),
wiki_enabled: project.wiki_enabled?)
end
def uploads_restorer
- Gitlab::ImportExport::UploadsRestorer.new(project: project_tree.restored_project, shared: shared)
+ Gitlab::ImportExport::UploadsRestorer.new(project: project, shared: shared)
end
def lfs_restorer
- Gitlab::ImportExport::LfsRestorer.new(project: project_tree.restored_project, shared: shared)
+ Gitlab::ImportExport::LfsRestorer.new(project: project, shared: shared)
end
def statistics_restorer
- Gitlab::ImportExport::StatisticsRestorer.new(project: project_tree.restored_project, shared: shared)
+ Gitlab::ImportExport::StatisticsRestorer.new(project: project, shared: shared)
end
def path_with_namespace
@@ -105,8 +105,6 @@ module Gitlab
end
def overwrite_project
- project = project_tree.restored_project
-
return unless can?(current_user, :admin_namespace, project.namespace)
if overwrite_project?
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 017e536c3e7..edee4ba2486 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -6,19 +6,21 @@ module Gitlab
# Relations which cannot be saved at project level (and have a group assigned)
GROUP_MODELS = [GroupLabel, Milestone].freeze
+ attr_reader :user
+ attr_reader :shared
+ attr_reader :project
+
def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json')
@user = user
@shared = shared
@project = project
- @project_id = project.id
@saved = true
end
def restore
begin
- json = IO.read(@path)
- @tree_hash = ActiveSupport::JSON.decode(json)
+ @tree_hash = read_tree_hash
rescue => e
Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
@@ -30,26 +32,31 @@ module Gitlab
ActiveRecord::Base.uncached do
ActiveRecord::Base.no_touching do
+ update_project_params
create_relations
end
end
+
+ # ensure that we have latest version of the restore
+ @project.reload # rubocop:disable Cop/ActiveRecordAssociationReload
+
+ true
rescue => e
@shared.error(e)
false
end
- def restored_project
- return @project unless @tree_hash
+ private
- @restored_project ||= restore_project
+ def read_tree_hash
+ json = IO.read(@path)
+ ActiveSupport::JSON.decode(json)
end
- private
-
def members_mapper
@members_mapper ||= Gitlab::ImportExport::MembersMapper.new(exported_members: @project_members,
user: @user,
- project: restored_project)
+ project: @project)
end
# A Hash of the imported merge request ID -> imported ID.
@@ -83,12 +90,11 @@ module Gitlab
remove_group_models(relation_hash) if relation_hash.is_a?(Array)
- @saved = false unless restored_project.append_or_update_attribute(relation_key, relation_hash)
+ @saved = false unless @project.append_or_update_attribute(relation_key, relation_hash)
save_id_mappings(relation_key, relation_hash_batch, relation_hash)
- # Restore the project again, extra query that skips holding the AR objects in memory
- @restored_project = Project.find(@project_id)
+ @project.reset
end
# Older, serialized CI pipeline exports may only have a
@@ -127,12 +133,10 @@ module Gitlab
reader.attributes_finder.find_relations_tree(:project)
end
- def restore_project
+ def update_project_params
Gitlab::Timeless.timeless(@project) do
@project.update(project_params)
end
-
- @project
end
def project_params
@@ -184,18 +188,9 @@ module Gitlab
return if tree_hash[relation_key].blank?
tree_array = [tree_hash[relation_key]].flatten
- null_iid_pipelines = []
# Avoid keeping a possible heavy object in memory once we are done with it
- while relation_item = (tree_array.shift || null_iid_pipelines.shift)
- if nil_iid_pipeline?(relation_key, relation_item) && tree_array.any?
- # Move pipelines with NULL IIDs to the end
- # so they don't clash with existing IIDs.
- null_iid_pipelines << relation_item
-
- next
- end
-
+ while relation_item = tree_array.shift
remove_feature_dependent_sub_relations(relation_item)
# The transaction at this level is less speedy than one single transaction
@@ -245,7 +240,7 @@ module Gitlab
members_mapper: members_mapper,
merge_requests_mapping: merge_requests_mapping,
user: @user,
- project: @restored_project,
+ project: @project,
excluded_keys: excluded_keys_for_relation(relation_key))
end.compact
@@ -259,10 +254,6 @@ module Gitlab
def excluded_keys_for_relation(relation)
reader.attributes_finder.find_excluded_keys(relation)
end
-
- def nil_iid_pipeline?(relation_key, relation_item)
- relation_key == 'ci_pipelines' && relation_item['iid'].nil?
- end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e94cde289b0..b62b133f461 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1692,6 +1692,12 @@ msgstr ""
msgid "Apply suggestion"
msgstr ""
+msgid "Apply template"
+msgstr ""
+
+msgid "Applying a template will replace the existing issue description. Any changes you have made will be lost."
+msgstr ""
+
msgid "Applying command"
msgstr ""
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index 18eadb7c4a3..c06fee92d09 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -92,6 +92,9 @@ describe 'issuable templates', :js do
context 'user creates a merge request using templates' do
let(:template_content) { 'this is a test "feature-proposal" template' }
+ let(:bug_template_content) { 'this is merge request bug template' }
+ let(:template_override_warning) { 'Applying a template will replace the existing issue description.' }
+ let(:updated_description) { 'updated merge request description' }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: project) }
before do
@@ -101,6 +104,12 @@ describe 'issuable templates', :js do
template_content,
message: 'added merge request template',
branch_name: 'master')
+ project.repository.create_file(
+ user,
+ '.gitlab/merge_request_templates/bug.md',
+ bug_template_content,
+ message: 'added merge request bug template',
+ branch_name: 'master')
visit edit_project_merge_request_path project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title'
end
@@ -111,6 +120,27 @@ describe 'issuable templates', :js do
assert_template
save_changes
end
+
+ context 'changes template' do
+ before do
+ select_template 'bug'
+ wait_for_requests
+ fill_in :'merge_request[description]', with: updated_description
+ select_template 'feature-proposal'
+ expect(page).to have_content template_override_warning
+ end
+
+ it 'user selects "bug" template, then updates description, then selects "feature-proposal" template, then cancels template change' do
+ page.find('.js-template-warning .js-cancel-btn').click
+ expect(find('textarea')['value']).to eq(updated_description)
+ end
+
+ it 'user selects "bug" template, then updates description, then selects "feature-proposal" template, then applies template change' do
+ page.find('.js-template-warning .js-override-template').click
+ wait_for_requests
+ assert_template
+ end
+ end
end
context 'user creates a merge request from a forked project using templates' do
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index 5ce85a37121..1f5da093902 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -4,6 +4,18 @@ import timeagoMixin from '~/vue_shared/mixins/timeago';
import { first } from 'underscore';
import { release } from '../mock_data';
import Icon from '~/vue_shared/components/icon.vue';
+import { scrollToElement } from '~/lib/utils/common_utils';
+
+let mockLocationHash;
+jest.mock('~/lib/utils/url_utility', () => ({
+ __esModule: true,
+ getLocationHash: jest.fn().mockImplementation(() => mockLocationHash),
+}));
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ __esModule: true,
+ scrollToElement: jest.fn(),
+}));
describe('Release block', () => {
let wrapper;
@@ -159,4 +171,61 @@ describe('Release block', () => {
expect(wrapper.text()).toContain('Upcoming Release');
});
+
+ it('slugifies the tag_name before setting it as the elements ID', () => {
+ const releaseClone = JSON.parse(JSON.stringify(release));
+ releaseClone.tag_name = 'a dangerous tag name <script>alert("hello")</script>';
+
+ factory(releaseClone);
+
+ expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script-');
+ });
+
+ describe('anchor scrolling', () => {
+ beforeEach(() => {
+ scrollToElement.mockClear();
+ });
+
+ const hasTargetBlueBackground = () => wrapper.classes('bg-line-target-blue');
+
+ it('does not attempt to scroll the page if no anchor tag is included in the URL', () => {
+ mockLocationHash = '';
+ factory(release);
+
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+
+ it("does not attempt to scroll the page if the anchor tag doesn't match the release's tag name", () => {
+ mockLocationHash = 'v0.4';
+ factory(release);
+
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+
+ it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => {
+ mockLocationHash = release.tag_name;
+ factory(release);
+
+ expect(scrollToElement).toHaveBeenCalledTimes(1);
+ expect(scrollToElement).toHaveBeenCalledWith(wrapper.element);
+ });
+
+ it('renders with a light blue background if it is the target of the anchor', () => {
+ mockLocationHash = release.tag_name;
+ factory(release);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTargetBlueBackground()).toBe(true);
+ });
+ });
+
+ it('does not render with a light blue background if it is not the target of the anchor', () => {
+ mockLocationHash = '';
+ factory(release);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(hasTargetBlueBackground()).toBe(false);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
index 42bf3b7df09..1949bee1406 100644
--- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -207,7 +207,7 @@ describe('Deployment component', () => {
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
});
@@ -223,12 +223,12 @@ describe('Deployment component', () => {
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
- expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
- const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag');
+ const deployUrl = vm.$el.querySelector('.js-deploy-url');
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(deployUrl).not.toBeNull();
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index c619a2ab237..218031784cb 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -520,20 +520,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
- describe '#restored_project' do
+ context 'Minimal JSON' do
let(:project) { create(:project) }
let(:tree_hash) { { 'visibility_level' => visibility } }
let(:restorer) { described_class.new(user: nil, shared: shared, project: project) }
before do
- restorer.instance_variable_set(:@tree_hash, tree_hash)
+ expect(restorer).to receive(:read_tree_hash) { tree_hash }
end
context 'no group visibility' do
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
- expect(restorer.restored_project.visibility_level).to eq(visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(visibility)
end
end
@@ -544,7 +545,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'uses private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
- expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
@@ -561,7 +563,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
- expect(restorer.restored_project.visibility_level).to eq(group_visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(group_visibility)
end
end
@@ -570,7 +573,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PRIVATE }
it 'uses the project visibility' do
- expect(restorer.restored_project.visibility_level).to eq(visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(visibility)
end
end
@@ -579,14 +583,16 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
let(:visibility) { Gitlab::VisibilityLevel::PUBLIC }
it 'uses the group visibility' do
- expect(restorer.restored_project.visibility_level).to eq(group_visibility)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(group_visibility)
end
context 'with restricted internal visibility' do
it 'sets private visibility' do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
- expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ expect(restorer.restore).to eq(true)
+ expect(restorer.project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
end
diff --git a/spec/models/concerns/atomic_internal_id_spec.rb b/spec/models/concerns/atomic_internal_id_spec.rb
index 60e7e90e3f1..f9ca9660bdb 100644
--- a/spec/models/concerns/atomic_internal_id_spec.rb
+++ b/spec/models/concerns/atomic_internal_id_spec.rb
@@ -9,16 +9,10 @@ describe AtomicInternalId do
let(:scope_attrs) { { project: milestone.project } }
let(:usage) { :milestones }
- describe '#ensure_project_iid!' do
- subject { milestone.ensure_project_iid! }
-
- it 'generates a new value if non is present' do
- expect(InternalId).to receive(:generate_next).with(milestone, scope_attrs, usage, anything).and_return(iid)
+ describe '#track_project_iid!' do
+ subject { milestone.track_project_iid! }
- expect { subject }.to change { milestone.iid }.from(nil).to(iid.to_i)
- end
-
- it 'tracks the present value if not generated by InternalId.generate_next' do
+ it 'tracks the present value' do
milestone.iid = external_iid
expect(InternalId).to receive(:track_greatest).once.with(milestone, scope_attrs, usage, external_iid, anything)
@@ -27,27 +21,51 @@ describe AtomicInternalId do
subject
end
- it 'generates a new value if first set with iid= but later set to nil' do
- expect(InternalId).to receive(:generate_next).with(milestone, scope_attrs, usage, anything).and_return(iid)
-
- milestone.iid = external_iid
- milestone.iid = nil
+ context 'when value is set by ensure_project_iid!' do
+ context 'with iid_always_track true' do
+ before do
+ stub_feature_flags(iid_always_track: false)
+ end
- expect { subject }.to change { milestone.iid }.from(nil).to(iid.to_i)
- end
+ it 'does not track the value' do
+ expect(InternalId).not_to receive(:track_greatest)
- context 'with iid_always_track disabled' do
- before do
- stub_feature_flags(iid_always_track: false)
+ milestone.ensure_project_iid!
+ subject
+ end
end
- it 'does not track the present value if generated by InternalId.generate_next' do
- milestone.ensure_project_iid!
+ context 'with iid_always_track enabled' do
+ before do
+ stub_feature_flags(iid_always_track: true)
+ end
- expect(InternalId).not_to receive(:track_greatest)
+ it 'does not track the value' do
+ expect(InternalId).to receive(:track_greatest)
- subject
+ milestone.ensure_project_iid!
+ subject
+ end
end
end
end
+
+ describe '#ensure_project_iid!' do
+ subject { milestone.ensure_project_iid! }
+
+ it 'generates a new value if non is present' do
+ expect(InternalId).to receive(:generate_next).with(milestone, scope_attrs, usage, anything).and_return(iid)
+
+ expect { subject }.to change { milestone.iid }.from(nil).to(iid.to_i)
+ end
+
+ it 'generates a new value if first set with iid= but later set to nil' do
+ expect(InternalId).to receive(:generate_next).with(milestone, scope_attrs, usage, anything).and_return(iid)
+
+ milestone.iid = external_iid
+ milestone.iid = nil
+
+ expect { subject }.to change { milestone.iid }.from(nil).to(iid.to_i)
+ end
+ end
end