summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-15 12:09:18 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-15 12:09:18 +0000
commitb7c735c8ac11b8182807070fc6f84f2606e15427 (patch)
treee74b4d25abb8bbf23546f001dd94515e2840a3a3
parent221b529789f4090341a825695aeb49b8df6dd11d (diff)
downloadgitlab-ce-b7c735c8ac11b8182807070fc6f84f2606e15427.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/integrations/edit/components/active_toggle.vue53
-rw-r--r--app/assets/javascripts/integrations/edit/event_hub.js3
-rw-r--r--app/assets/javascripts/integrations/edit/index.js30
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js29
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue17
-rw-r--r--app/controllers/projects/releases_controller.rb2
-rw-r--r--app/helpers/snippets_helper.rb10
-rw-r--r--app/models/diff_note_position.rb15
-rw-r--r--app/models/note.rb1
-rw-r--r--app/models/performance_monitoring/prometheus_panel.rb2
-rw-r--r--app/services/discussions/capture_diff_note_position_service.rb65
-rw-r--r--app/services/discussions/capture_diff_note_positions_service.rb34
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb7
-rw-r--r--app/services/notes/create_service.rb4
-rw-r--r--app/views/groups/settings/_permissions.html.haml1
-rw-r--r--app/views/shared/_service_settings.html.haml6
-rw-r--r--app/views/shared/snippets/_snippet.html.haml5
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/background_migration_worker.rb1
-rw-r--r--changelogs/unreleased/199882-update-active-checkbox-component.yml5
-rw-r--r--changelogs/unreleased/214320-deprecate-returning-token-from-runners-api.yml5
-rw-r--r--changelogs/unreleased/214360-custom-metrics-dashboard-does-not-load-when-max_value-is-set.yml6
-rw-r--r--changelogs/unreleased/31015-migrate-legacy-attachments.yml5
-rw-r--r--changelogs/unreleased/nfriend-enable-release_asset_link_editing-fleature-flag.yml5
-rw-r--r--db/post_migrate/20200409211607_migrate_legacy_attachments.rb30
-rw-r--r--db/structure.sql1
-rw-r--r--doc/administration/raketasks/uploads/migrate.md18
-rw-r--r--doc/api/runners.md8
-rw-r--r--doc/user/group/saml_sso/index.md26
-rw-r--r--doc/user/project/integrations/bamboo.md2
-rw-r--r--doc/user/project/integrations/discord_notifications.md2
-rw-r--r--doc/user/project/integrations/generic_alerts.md2
-rw-r--r--doc/user/project/integrations/github.md2
-rw-r--r--doc/user/project/integrations/hangouts_chat.md2
-rw-r--r--doc/user/project/integrations/hipchat.md2
-rw-r--r--doc/user/project/integrations/irker.md2
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md2
-rw-r--r--doc/user/project/integrations/slack.md2
-rw-r--r--doc/user/project/integrations/slack_slash_commands.md2
-rw-r--r--doc/user/project/integrations/unify_circuit.md2
-rw-r--r--doc/user/project/releases/index.md4
-rw-r--r--lib/api/entities/runner_details.rb4
-rw-r--r--lib/gitlab/import_export/project/relation_factory.rb3
-rw-r--r--lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb3
-rw-r--r--lib/tasks/gitlab/uploads/legacy.rake27
-rw-r--r--locale/gitlab.pot23
-rw-r--r--spec/features/admin/admin_settings_spec.rb1
-rw-r--r--spec/features/dashboard/snippets_spec.rb52
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb3
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb5
-rw-r--r--spec/features/projects/services/user_activates_slack_slash_command_spec.rb9
-rw-r--r--spec/features/projects/services/user_activates_youtrack_spec.rb2
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml1
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json1
-rw-r--r--spec/frontend/integrations/edit/components/active_toggle_spec.js65
-rw-r--r--spec/frontend/notes/components/note_header_spec.js42
-rw-r--r--spec/helpers/snippets_helper_spec.rb31
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js21
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml4
-rw-r--r--spec/lib/gitlab/import_export/project/tree_restorer_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/project/tree_saver_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb17
-rw-r--r--spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb19
-rw-r--r--spec/models/diff_note_position_spec.rb37
-rw-r--r--spec/services/discussions/capture_diff_note_position_service_spec.rb31
-rw-r--r--spec/services/discussions/capture_diff_note_positions_service_spec.rb67
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb18
-rw-r--r--spec/services/notes/create_service_spec.rb13
-rw-r--r--spec/support/helpers/test_env.rb4
-rw-r--r--spec/support/import_export/common_util.rb9
71 files changed, 800 insertions, 142 deletions
diff --git a/app/assets/javascripts/integrations/edit/components/active_toggle.vue b/app/assets/javascripts/integrations/edit/components/active_toggle.vue
new file mode 100644
index 00000000000..2b0aa2586e4
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/components/active_toggle.vue
@@ -0,0 +1,53 @@
+<script>
+import eventHub from '../event_hub';
+import { GlToggle } from '@gitlab/ui';
+
+export default {
+ name: 'ActiveToggle',
+ components: {
+ GlToggle,
+ },
+ props: {
+ initialActivated: {
+ type: Boolean,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ activated: this.initialActivated,
+ };
+ },
+ mounted() {
+ // Initialize view
+ this.$nextTick(() => {
+ this.onToggle(this.activated);
+ });
+ },
+ methods: {
+ onToggle(e) {
+ eventHub.$emit('toggle', e);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="form-group row" role="group">
+ <label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
+ <div class="col-sm-10 pt-1">
+ <gl-toggle
+ v-model="activated"
+ :disabled="disabled"
+ name="service[active]"
+ @change="onToggle"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/integrations/edit/event_hub.js b/app/assets/javascripts/integrations/edit/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js
new file mode 100644
index 00000000000..a2ba581d429
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import ActiveToggle from './components/active_toggle.vue';
+
+export default el => {
+ if (!el) {
+ return null;
+ }
+
+ const { showActive: showActiveStr, activated: activatedStr, disabled: disabledStr } = el.dataset;
+ const showActive = parseBoolean(showActiveStr);
+ const activated = parseBoolean(activatedStr);
+ const disabled = parseBoolean(disabledStr);
+
+ if (!showActive) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(ActiveToggle, {
+ props: {
+ initialActivated: activated,
+ disabled,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 1c9b94ade8a..3067f4090b1 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -2,28 +2,33 @@ import $ from 'jquery';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
import { __ } from '~/locale';
+import initForm from './edit';
+import eventHub from './edit/event_hub';
export default class IntegrationSettingsForm {
constructor(formSelector) {
this.$form = $(formSelector);
+ this.formActive = false;
// Form Metadata
this.canTestService = this.$form.data('canTest');
this.testEndPoint = this.$form.data('testUrl');
// Form Child Elements
- this.$serviceToggle = this.$form.find('#service_active');
this.$submitBtn = this.$form.find('button[type="submit"]');
this.$submitBtnLoader = this.$submitBtn.find('.js-btn-spinner');
this.$submitBtnLabel = this.$submitBtn.find('.js-btn-label');
}
init() {
- // Initialize View
- this.toggleServiceState(this.$serviceToggle.is(':checked'));
+ // Init Vue component
+ initForm(document.querySelector('.js-vue-integration-settings'));
+ eventHub.$on('toggle', active => {
+ this.formActive = active;
+ this.handleServiceToggle();
+ });
// Bind Event Listeners
- this.$serviceToggle.on('change', e => this.handleServiceToggle(e));
this.$submitBtn.on('click', e => this.handleSettingsSave(e));
}
@@ -31,7 +36,7 @@ export default class IntegrationSettingsForm {
// Check if Service is marked active, as if not marked active,
// We can skip testing it and directly go ahead to allow form to
// be submitted
- if (!this.$serviceToggle.is(':checked')) {
+ if (!this.formActive) {
return;
}
@@ -47,16 +52,16 @@ export default class IntegrationSettingsForm {
}
}
- handleServiceToggle(e) {
- this.toggleServiceState($(e.currentTarget).is(':checked'));
+ handleServiceToggle() {
+ this.toggleServiceState();
}
/**
* Change Form's validation enforcement based on service status (active/inactive)
*/
- toggleServiceState(serviceActive) {
- this.toggleSubmitBtnLabel(serviceActive);
- if (serviceActive) {
+ toggleServiceState() {
+ this.toggleSubmitBtnLabel();
+ if (this.formActive) {
this.$form.removeAttr('novalidate');
} else if (!this.$form.attr('novalidate')) {
this.$form.attr('novalidate', 'novalidate');
@@ -66,10 +71,10 @@ export default class IntegrationSettingsForm {
/**
* Toggle Submit button label based on Integration status and ability to test service
*/
- toggleSubmitBtnLabel(serviceActive) {
+ toggleSubmitBtnLabel() {
let btnLabel = __('Save changes');
- if (serviceActive && this.canTestService) {
+ if (this.formActive && this.canTestService) {
btnLabel = __('Test settings and save changes');
}
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 9cb592ceedb..f82b3554cac 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -39,13 +39,18 @@ export default {
required: false,
default: true,
},
+ showSpinner: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
toggleChevronClass() {
return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
},
noteTimestampLink() {
- return `#note_${this.noteId}`;
+ return this.noteId ? `#note_${this.noteId}` : undefined;
},
hasAuthor() {
return this.author && Object.keys(this.author).length;
@@ -60,7 +65,9 @@ export default {
this.$emit('toggleHandler');
},
updateTargetNoteHash() {
- this.setTargetNoteHash(this.noteTimestampLink);
+ if (this.$store) {
+ this.setTargetNoteHash(this.noteTimestampLink);
+ }
},
},
};
@@ -101,16 +108,20 @@ export default {
<template v-if="actionText">{{ actionText }}</template>
</span>
<a
- ref="noteTimestamp"
+ v-if="noteTimestampLink"
+ ref="noteTimestampLink"
:href="noteTimestampLink"
class="note-timestamp system-note-separator"
@click="updateTargetNoteHash"
>
<time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
</a>
+ <time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</template>
<slot name="extra-controls"></slot>
<i
+ v-if="showSpinner"
+ ref="spinner"
class="fa fa-spinner fa-spin editing-spinner"
:aria-label="__('Comment is being updated')"
aria-hidden="true"
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 010e9411c7d..228b139d794 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -9,7 +9,7 @@ class Projects::ReleasesController < Projects::ApplicationController
push_frontend_feature_flag(:release_issue_summary, project, default_enabled: true)
push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true)
push_frontend_feature_flag(:release_show_page, project, default_enabled: true)
- push_frontend_feature_flag(:release_asset_link_editing, project)
+ push_frontend_feature_flag(:release_asset_link_editing, project, default_enabled: true)
end
before_action :authorize_update_release!, only: %i[edit update]
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index fd7e58826b5..a9f90a8f5e4 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -160,4 +160,14 @@ module SnippetsHelper
title: 'Download',
rel: 'noopener noreferrer')
end
+
+ def snippet_file_name(snippet)
+ blob = if Feature.enabled?(:version_snippets, current_user) && !snippet.repository.empty?
+ snippet.blobs.first
+ else
+ snippet.blob
+ end
+
+ blob.name
+ end
end
diff --git a/app/models/diff_note_position.rb b/app/models/diff_note_position.rb
index 78e4fbc49eb..716a56c6430 100644
--- a/app/models/diff_note_position.rb
+++ b/app/models/diff_note_position.rb
@@ -28,9 +28,20 @@ class DiffNotePosition < ApplicationRecord
end
def position=(position)
+ assign_attributes(self.class.position_to_attrs(position))
+ end
+
+ def self.create_or_update_for(note, params)
+ attrs = position_to_attrs(params[:position])
+ attrs.merge!(params.slice(:diff_type, :line_code))
+ attrs[:note_id] = note.id
+
+ upsert(attrs, unique_by: [:note_id, :diff_type])
+ end
+
+ def self.position_to_attrs(position)
position_attrs = position.to_h
position_attrs[:diff_content_type] = position_attrs.delete(:position_type)
-
- assign_attributes(position_attrs)
+ position_attrs
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 251a75e6025..e6ad7c2227f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -83,6 +83,7 @@ class Note < ApplicationRecord
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata
has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id
+ has_many :diff_note_positions
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
diff --git a/app/models/performance_monitoring/prometheus_panel.rb b/app/models/performance_monitoring/prometheus_panel.rb
index 0381c5cff10..3fe029abda0 100644
--- a/app/models/performance_monitoring/prometheus_panel.rb
+++ b/app/models/performance_monitoring/prometheus_panel.rb
@@ -4,7 +4,7 @@ module PerformanceMonitoring
class PrometheusPanel
include ActiveModel::Model
- attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis
+ attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis, :max_value
validates :title, presence: true
validates :metrics, presence: true
diff --git a/app/services/discussions/capture_diff_note_position_service.rb b/app/services/discussions/capture_diff_note_position_service.rb
new file mode 100644
index 00000000000..273a60f7e55
--- /dev/null
+++ b/app/services/discussions/capture_diff_note_position_service.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module Discussions
+ class CaptureDiffNotePositionService
+ def initialize(merge_request, paths)
+ @project = merge_request.project
+ @tracer = build_tracer(merge_request, paths)
+ end
+
+ def execute(discussion)
+ # The service has been implemented for text only
+ # The impact of image notes on this service is being investigated in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/213989
+ return unless discussion.on_text?
+
+ result = tracer&.trace(discussion.position)
+ return unless result
+
+ position = result[:position]
+
+ # Currently position data is copied across all notes of a discussion
+ # It makes sense to store a position only for the first note instead
+ # Within the newly introduced table we can start doing just that
+ DiffNotePosition.create_or_update_for(discussion.notes.first,
+ diff_type: :head,
+ position: position,
+ line_code: position.line_code(project.repository))
+ end
+
+ private
+
+ attr_reader :tracer, :project
+
+ def build_tracer(merge_request, paths)
+ return if paths.blank?
+
+ old_diff_refs, new_diff_refs = build_diff_refs(merge_request)
+
+ return unless old_diff_refs && new_diff_refs
+
+ Gitlab::Diff::PositionTracer.new(
+ project: project,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs,
+ paths: paths.uniq)
+ end
+
+ def build_diff_refs(merge_request)
+ merge_ref_head = merge_request.merge_ref_head
+ return unless merge_ref_head
+
+ start_sha, base_sha = merge_ref_head.parent_ids
+ new_diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: base_sha,
+ start_sha: start_sha,
+ head_sha: merge_ref_head.id)
+
+ old_diff_refs = merge_request.diff_refs
+
+ return if new_diff_refs == old_diff_refs
+
+ [old_diff_refs, new_diff_refs]
+ end
+ end
+end
diff --git a/app/services/discussions/capture_diff_note_positions_service.rb b/app/services/discussions/capture_diff_note_positions_service.rb
new file mode 100644
index 00000000000..3684a3f679a
--- /dev/null
+++ b/app/services/discussions/capture_diff_note_positions_service.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Discussions
+ class CaptureDiffNotePositionsService
+ def initialize(merge_request)
+ @merge_request = merge_request
+ end
+
+ def execute
+ return unless merge_request.has_complete_diff_refs?
+
+ discussions, paths = build_discussions
+
+ service = Discussions::CaptureDiffNotePositionService.new(merge_request, paths)
+
+ discussions.each do |discussion|
+ service.execute(discussion)
+ end
+ end
+
+ private
+
+ attr_reader :merge_request
+
+ def build_discussions
+ active_diff_discussions = merge_request.notes.new_diff_notes.discussions.select do |discussion|
+ discussion.active?(merge_request.diff_refs)
+ end
+ paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }
+
+ [active_diff_discussions, paths]
+ end
+ end
+end
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
index 5b79e4d01f2..d3d661a3b75 100644
--- a/app/services/merge_requests/mergeability_check_service.rb
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -118,11 +118,18 @@ module MergeRequests
if can_git_merge? && merge_to_ref
merge_request.mark_as_mergeable
+ update_diff_discussion_positions!
else
merge_request.mark_as_unmergeable
end
end
+ def update_diff_discussion_positions!
+ return if Feature.disabled?(:merge_ref_head_comments, merge_request.target_project)
+
+ Discussions::CaptureDiffNotePositionsService.new(merge_request).execute
+ end
+
def recheck!
if !merge_request.recheck_merge_status? && outdated_merge_ref?
merge_request.mark_as_unchecked
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 80bc4485988..6c1f52ec866 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -65,6 +65,10 @@ module Notes
if Feature.enabled?(:notes_create_service_tracking, project)
Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
end
+
+ if Feature.enabled?(:merge_ref_head_comments, project) && note.for_merge_request? && note.diff_note? && note.start_of_discussion?
+ Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
+ end
end
def do_commands(note, update_params, message, only_commands)
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 016a9c8e054..1ddaa855e62 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -37,6 +37,7 @@
= render 'groups/settings/project_creation_level', f: f, group: @group
= render 'groups/settings/subgroup_creation_level', f: f, group: @group
= render 'groups/settings/two_factor_auth', f: f
+ = render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
= f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index aeda7ea9909..3da4b77b5eb 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -8,11 +8,7 @@
= markdown @service.help
.service-settings
- - if @service.show_active_box?
- .form-group.row
- = form.label :active, "Active", class: "col-form-label col-sm-2"
- .col-sm-10
- = form.check_box :active, checked: @service.active || @service.new_record?, disabled: disable_fields_service?(@service)
+ .js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, disabled: disable_fields_service?(@service).to_s } }
- if @service.configurable_events.present?
.form-group.row
diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml
index 9e038854c59..3fea2c1e3fc 100644
--- a/app/views/shared/snippets/_snippet.html.haml
+++ b/app/views/shared/snippets/_snippet.html.haml
@@ -1,5 +1,6 @@
- link_project = local_assigns.fetch(:link_project, false)
- notes_count = @noteable_meta_data[snippet.id].user_notes_count
+- file_name = snippet_file_name(snippet)
%li.snippet-row.py-3
= image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 d-none d-sm-block", alt: ''
@@ -7,10 +8,10 @@
.title
= link_to gitlab_snippet_path(snippet) do
= snippet.title
- - if snippet.file_name.present?
+ - if file_name.present?
%span.snippet-filename.d-none.d-sm-inline-block.ml-2
= sprite_icon('doc-code', size: 16, css_class: 'file-icon align-text-bottom')
- = snippet.file_name
+ = file_name
%ul.controls
%li
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 5e3ea162d20..38f518458d6 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -944,7 +944,7 @@
- :name: background_migration
:feature_category: :database
:has_external_dependencies:
- :urgency: :low
+ :urgency: :throttled
:resource_boundary: :unknown
:weight: 1
:idempotent:
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index f5fd73156b3..6a64afe47de 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -4,6 +4,7 @@ class BackgroundMigrationWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
feature_category :database
+ urgency :throttled
# The minimum amount of time between processing two jobs of the same migration
# class.
diff --git a/changelogs/unreleased/199882-update-active-checkbox-component.yml b/changelogs/unreleased/199882-update-active-checkbox-component.yml
new file mode 100644
index 00000000000..4926a45be8f
--- /dev/null
+++ b/changelogs/unreleased/199882-update-active-checkbox-component.yml
@@ -0,0 +1,5 @@
+---
+title: Update Active checkbox component to use toggle
+merge_request: 27778
+author:
+type: added
diff --git a/changelogs/unreleased/214320-deprecate-returning-token-from-runners-api.yml b/changelogs/unreleased/214320-deprecate-returning-token-from-runners-api.yml
new file mode 100644
index 00000000000..18f868131e1
--- /dev/null
+++ b/changelogs/unreleased/214320-deprecate-returning-token-from-runners-api.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate 'token' attribute from Runners API
+merge_request: 29481
+author:
+type: deprecated
diff --git a/changelogs/unreleased/214360-custom-metrics-dashboard-does-not-load-when-max_value-is-set.yml b/changelogs/unreleased/214360-custom-metrics-dashboard-does-not-load-when-max_value-is-set.yml
new file mode 100644
index 00000000000..b50c3e88655
--- /dev/null
+++ b/changelogs/unreleased/214360-custom-metrics-dashboard-does-not-load-when-max_value-is-set.yml
@@ -0,0 +1,6 @@
+---
+title: Fix dashboard processing error which prevented dashboards with unknown attributes
+ inside panels from being displayed
+merge_request: 29517
+author:
+type: fixed
diff --git a/changelogs/unreleased/31015-migrate-legacy-attachments.yml b/changelogs/unreleased/31015-migrate-legacy-attachments.yml
new file mode 100644
index 00000000000..fdc52fa78c4
--- /dev/null
+++ b/changelogs/unreleased/31015-migrate-legacy-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate legacy uploads out of deprecated paths
+merge_request: 29295
+author:
+type: fixed
diff --git a/changelogs/unreleased/nfriend-enable-release_asset_link_editing-fleature-flag.yml b/changelogs/unreleased/nfriend-enable-release_asset_link_editing-fleature-flag.yml
new file mode 100644
index 00000000000..4ff7b6d477c
--- /dev/null
+++ b/changelogs/unreleased/nfriend-enable-release_asset_link_editing-fleature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Release links to be edited on the Edit Release page
+merge_request: 28816
+author:
+type: added
diff --git a/db/post_migrate/20200409211607_migrate_legacy_attachments.rb b/db/post_migrate/20200409211607_migrate_legacy_attachments.rb
new file mode 100644
index 00000000000..fb4a996d3b7
--- /dev/null
+++ b/db/post_migrate/20200409211607_migrate_legacy_attachments.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class MigrateLegacyAttachments < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ MIGRATION = 'LegacyUploadsMigrator'
+ BATCH_SIZE = 5000
+ INTERVAL = 5.minutes.to_i
+
+ class Upload < ActiveRecord::Base
+ self.table_name = 'uploads'
+
+ include ::EachBatch
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(Upload.where(uploader: 'AttachmentUploader', model_type: 'Note'),
+ MIGRATION,
+ INTERVAL,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index d1fdcc9e465..28b0010eb30 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -13152,5 +13152,6 @@ COPY "schema_migrations" (version) FROM STDIN;
20200408110856
20200408153842
20200408175424
+20200409211607
\.
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index d2823847a89..851305d433f 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -111,24 +111,6 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeReque
sudo -u git -H bundle exec rake "gitlab:uploads:migrate[DesignManagement::DesignV432x230Uploader, DesignManagement::Action]"
```
-## Migrate legacy uploads out of deprecated paths
-
-> Introduced in GitLab 12.3.
-
-To migrate all uploads created by legacy uploaders, run:
-
-**Omnibus Installation**
-
-```shell
-gitlab-rake gitlab:uploads:legacy:migrate
-```
-
-**Source Installation**
-
-```shell
-bundle exec rake gitlab:uploads:legacy:migrate
-```
-
## Migrate from object storage to local storage
If you need to disable Object Storage for any reason, first you need to migrate
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 523f0363cee..21d768a1605 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -162,6 +162,10 @@ GET /runners/:id
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6"
```
+CAUTION: **Deprecation**
+The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320).
+It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322).
+
Example response:
```json
@@ -221,6 +225,10 @@ PUT /runners/:id
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
+CAUTION: **Deprecation**
+The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320).
+It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322).
+
Example response:
```json
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 2b4170d21af..e78a894d987 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -112,6 +112,32 @@ To access the Credentials inventory of a group, navigate to **{shield}** **Secur
This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
+##### Limiting lifetime of personal access tokens of users in Group-managed accounts **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118893) in GitLab 12.10.
+
+Users in a group managed account can optionally specify an expiration date for
+[personal access tokens](../../profile/personal_access_tokens.md).
+This expiration date is not a requirement, and can be set to any arbitrary date.
+
+Since personal access tokens are the only token needed for programmatic access to GitLab, organizations with security requirements may want to enforce more protection to require regular rotation of these tokens.
+
+###### Setting a limit
+
+Only a GitLab administrator or an owner of a Group-managed account can set a limit. Leaving it empty means that the [instance level restrictions](../../admin_area/settings/account_and_limit_settings.md#limiting-lifetime-of-personal-access-tokens-ultimate-only) on the lifetime of personal access tokens will apply.
+
+To set a limit on how long personal access tokens are valid for users in a group managed account:
+
+1. Navigate to the **{settings}** **Settings > General** page in your group's sidebar.
+1. Expand the **Permissions, LFS, 2FA** section.
+1. Fill in the **Maximum allowable lifetime for personal access tokens (days)** field.
+1. Click **Save changes**.
+
+Once a lifetime for personal access tokens is set, GitLab will:
+
+- Apply the lifetime for new personal access tokens, and require users managed by the group to set an expiration date that is no later than the allowed lifetime.
+- After three hours, revoke old tokens with no expiration date or with a lifetime longer than the allowed lifetime. Three hours is given to allow administrators/group owner to change the allowed lifetime, or remove it, before revocation takes place.
+
##### Outer forks restriction for Group-managed accounts
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34648) in GitLab 12.9.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 726b5f657ea..8c7e6edbf38 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -39,7 +39,7 @@ service in GitLab.
1. Navigate to the project you want to configure to trigger builds.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click 'Atlassian Bamboo CI'
-1. Select the 'Active' checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Enter the base URL of your Bamboo server. `https://bamboo.example.com`
1. Enter the build key from your Bamboo build plan. Build keys are typically made
up from the Project Key and Plan Key that are set on project/plan creation and
diff --git a/doc/user/project/integrations/discord_notifications.md b/doc/user/project/integrations/discord_notifications.md
index 9252b7dd0f9..aa45cc38cb5 100644
--- a/doc/user/project/integrations/discord_notifications.md
+++ b/doc/user/project/integrations/discord_notifications.md
@@ -21,7 +21,7 @@ With the webhook URL created in the Discord channel, you can set up the Discord
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings. That is, **Project > Settings > Integrations**.
1. Select the **Discord Notifications** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events for which you want to send notifications to Discord.
1. Paste the webhook URL that you copied from the create Discord webhook step.
1. Configure the remaining options and click the **Save changes** button.
diff --git a/doc/user/project/integrations/generic_alerts.md b/doc/user/project/integrations/generic_alerts.md
index f5d0f5eb21b..1a000fd1c44 100644
--- a/doc/user/project/integrations/generic_alerts.md
+++ b/doc/user/project/integrations/generic_alerts.md
@@ -18,7 +18,7 @@ To set up the generic alerts integration:
1. Navigate to **Settings > Integrations** in a project.
1. Click on **Alerts endpoint**.
-1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
+1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
## Customizing the payload
diff --git a/doc/user/project/integrations/github.md b/doc/user/project/integrations/github.md
index 2e3801f3a32..42d8eadd35e 100644
--- a/doc/user/project/integrations/github.md
+++ b/doc/user/project/integrations/github.md
@@ -27,7 +27,7 @@ with `repo:status` access granted:
1. Navigate to the project you want to configure.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "GitHub".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Paste the token you've generated on GitHub
1. Enter the path to your project on GitHub, such as `https://github.com/username/repository`
1. Optionally uncheck **Static status check names** checkbox to disable static status check names.
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index bd32ef91e3c..f33833a9c93 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -19,7 +19,7 @@ When you have the **Webhook URL** for your Hangouts Chat room webhook, you can s
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Hangouts Chat** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to receive.
1. Paste the **Webhook URL** that you copied from the Hangouts Chat configuration step.
1. Configure the remaining options and click `Save changes`.
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
index 1e04e28143a..2ed7f13db9b 100644
--- a/doc/user/project/integrations/hipchat.md
+++ b/doc/user/project/integrations/hipchat.md
@@ -37,7 +37,7 @@ service in GitLab.
1. Navigate to the project you want to configure for notifications.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "HipChat".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Insert the `token` field from the URL into the `Token` field on the Web page.
1. Insert the `room` field from the URL into the `Room` field on the Web page.
1. Save or optionally click "Test Settings".
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index db5397ee7d5..2d807d4302b 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -28,7 +28,7 @@ need to follow the firsts steps of the next section.
1. Navigate to the project you want to configure for notifications.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "Irker".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index 6abd613a019..a23d8d7306d 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -103,7 +103,7 @@ in a new slash command.
### Step 4. Copy the Mattermost token into the Mattermost slash command service
1. In GitLab, paste the Mattermost token you copied in the previous step and
- check the **Active** checkbox.
+ ensure that the **Active** toggle is enabled.
![Mattermost copy token to GitLab](img/mattermost_gitlab_token.png)
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 6ceb398fa17..ba2a8f55d84 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -14,7 +14,7 @@ The Slack Notifications Service allows your GitLab project to send events (e.g.
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Slack notifications** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to send to Slack as a notification.
1. For each event, optionally enter the Slack channel names where you want to send the event, separated by a comma. If left empty, the event will be sent to the default channel that you configured in the Slack Configuration step. **Note:** Usernames and private channels are not supported. To send direct messages, use the Member ID found under user's Slack profile.
1. Paste the **Webhook URL** that you copied from the Slack Configuration step.
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index d39d5cde46a..d25a367bd1f 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -19,7 +19,7 @@ For GitLab.com, use the [Slack app](gitlab_slack_application.md) instead.
1. Enter a trigger term. We suggest you use the project name. Click **Add Slash Command Integration**.
1. Complete the rest of the fields in the Slack configuration page using information from the GitLab browser tab. In particular, the URL needs to be copied and pasted. Click **Save Integration** to complete the configuration in Slack.
1. While still on the Slack configuration page, copy the **token**. Go back to the GitLab browser tab and paste in the **token**.
-1. Check the **Active** checkbox and click **Save changes** to complete the configuration in GitLab.
+1. Ensure that the **Active** toggle is enabled and click **Save changes** to complete the configuration in GitLab.
![Slack setup instructions](img/slack_setup.png)
diff --git a/doc/user/project/integrations/unify_circuit.md b/doc/user/project/integrations/unify_circuit.md
index 51f524f95c3..98dc6f298d5 100644
--- a/doc/user/project/integrations/unify_circuit.md
+++ b/doc/user/project/integrations/unify_circuit.md
@@ -17,7 +17,7 @@ When you have the **Webhook URL** for your Unify Circuit conversation webhook, y
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Unify Circuit** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to receive in Unify Circuit.
1. Paste the **Webhook URL** that you copied from the Unify Circuit configuration step.
1. Configure the remaining options and click `Save changes`.
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index 37da3d0bb4a..bae514153ac 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -309,12 +309,12 @@ Here is an example of a Release Evidence object:
### Enabling Release Evidence display **(CORE ONLY)**
This feature comes with the `:release_evidence_collection` feature flag
-disabled by default in GitLab self-managed instances. To turn it on,
+enabled by default in GitLab self-managed instances. To turn it off,
ask a GitLab administrator with Rails console access to run the following
command:
```ruby
-Feature.enable(:release_evidence_collection)
+Feature.disable(:release_evidence_collection)
```
NOTE: **Note:**
diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb
index 17202821e6e..2bb143253fe 100644
--- a/lib/api/entities/runner_details.rb
+++ b/lib/api/entities/runner_details.rb
@@ -10,7 +10,11 @@ module API
expose :access_level
expose :version, :revision, :platform, :architecture
expose :contacted_at
+
+ # @deprecated in 12.10 https://gitlab.com/gitlab-org/gitlab/-/issues/214320
+ # will be removed by 13.0 https://gitlab.com/gitlab-org/gitlab/-/issues/214322
expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? }
+
# rubocop: disable CodeReuse/ActiveRecord
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
if options[:current_user].admin?
diff --git a/lib/gitlab/import_export/project/relation_factory.rb b/lib/gitlab/import_export/project/relation_factory.rb
index 2405176c518..f7f1195f2f1 100644
--- a/lib/gitlab/import_export/project/relation_factory.rb
+++ b/lib/gitlab/import_export/project/relation_factory.rb
@@ -70,8 +70,7 @@ module Gitlab
# Do not create relation if it is:
# - An unknown service
# - A legacy trigger
- unknown_service? ||
- (!Feature.enabled?(:use_legacy_pipeline_triggers, @importable) && legacy_trigger?)
+ unknown_service? || legacy_trigger?
end
def setup_models
diff --git a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb b/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
index 301c54b9f23..239b5161256 100644
--- a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
+++ b/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
@@ -15,6 +15,9 @@ module Gitlab
insert_panel_id(id, panel)
end
+ rescue ActiveModel::UnknownAttributeError => error
+ remove_panel_ids!
+ Gitlab::ErrorTracking.log_exception(error)
end
private
diff --git a/lib/tasks/gitlab/uploads/legacy.rake b/lib/tasks/gitlab/uploads/legacy.rake
deleted file mode 100644
index 74db0060b8d..00000000000
--- a/lib/tasks/gitlab/uploads/legacy.rake
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-namespace :gitlab do
- namespace :uploads do
- namespace :legacy do
- desc "GitLab | Uploads | Migrate all legacy attachments"
- task migrate: :environment do
- class Upload < ApplicationRecord
- self.table_name = 'uploads'
-
- include ::EachBatch
- end
-
- migration = 'LegacyUploadsMigrator'
- batch_size = 5000
- delay_interval = 5.minutes.to_i
-
- Upload.where(uploader: 'AttachmentUploader', model_type: 'Note').each_batch(of: batch_size) do |relation, index|
- start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
- delay = index * delay_interval
-
- BackgroundMigrationWorker.perform_in(delay, migration, [start_id, end_id])
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3b1c8f0d706..9965f8c7d47 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -385,6 +385,11 @@ msgstr ""
msgid "%{name}'s avatar"
msgstr ""
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
@@ -10840,6 +10845,9 @@ msgstr ""
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
@@ -23015,6 +23023,15 @@ msgstr ""
msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
msgstr ""
@@ -25030,6 +25047,9 @@ msgstr ""
msgid "no contributions"
msgstr ""
+msgid "no expiration"
+msgstr ""
+
msgid "no one can merge"
msgstr ""
@@ -25315,6 +25335,9 @@ msgstr ""
msgid "view the blob"
msgstr ""
+msgid "vulnerability|Add a comment"
+msgstr ""
+
msgid "vulnerability|Add a comment or reason for dismissal"
msgstr ""
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index ed05ad60ff0..8eb15bb6bf5 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -489,7 +489,6 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc
end
def check_all_events
- page.check('Active')
page.check('Push')
page.check('Issue')
page.check('Confidential issue')
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index db5e56bdde0..287af7e7b11 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -3,8 +3,10 @@
require 'spec_helper'
describe 'Dashboard snippets' do
+ let_it_be(:user) { create(:user) }
+
context 'when the project has snippets' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, creator: user) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
@@ -22,7 +24,7 @@ describe 'Dashboard snippets' do
end
context 'when there are no project snippets', :js do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, creator: user) }
before do
sign_in(project.owner)
@@ -47,9 +49,49 @@ describe 'Dashboard snippets' do
end
end
+ context 'rendering file names' do
+ let_it_be(:snippet) { create(:personal_snippet, :public, author: user, file_name: 'foo.txt') }
+ let_it_be(:versioned_snippet) { create(:personal_snippet, :repository, :public, author: user, file_name: 'bar.txt') }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when feature flag :version_snippets is disabled' do
+ before do
+ stub_feature_flags(version_snippets: false)
+
+ visit dashboard_snippets_path
+ end
+
+ it 'contains the snippet file names from the DB' do
+ aggregate_failures do
+ expect(page).to have_content 'foo.txt'
+ expect(page).to have_content('bar.txt')
+ expect(page).not_to have_content('.gitattributes')
+ end
+ end
+ end
+
+ context 'when feature flag :version_snippets is enabled' do
+ before do
+ stub_feature_flags(version_snippets: true)
+
+ visit dashboard_snippets_path
+ end
+
+ it 'contains both the versioned and non-versioned filenames' do
+ aggregate_failures do
+ expect(page).to have_content 'foo.txt'
+ expect(page).to have_content('.gitattributes')
+ expect(page).not_to have_content('bar.txt')
+ end
+ end
+ end
+ end
+
context 'filtering by visibility' do
- let(:user) { create(:user) }
- let!(:snippets) do
+ let_it_be(:snippets) do
[
create(:personal_snippet, :public, author: user),
create(:personal_snippet, :internal, author: user),
@@ -99,7 +141,7 @@ describe 'Dashboard snippets' do
end
context 'as an external user' do
- let(:user) { create(:user, :external) }
+ let_it_be(:user) { create(:user, :external) }
before do
sign_in(user)
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 0b0a3362043..4f3fb6ac3bf 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -9,7 +9,7 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
def fill_short_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 557615f8872..fb9628032b2 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -10,7 +10,7 @@ describe 'User activates Jira', :js do
let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
def fill_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_url', with: url
fill_in 'service_username', with: 'username'
@@ -53,7 +53,6 @@ describe 'User activates Jira', :js do
it 'shows errors when some required fields are not filled in' do
click_link('Jira')
- check 'Active'
fill_in 'service_password', with: 'password'
click_button('Test settings and save changes')
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index 2eaa2d24c4b..ac9cb00be84 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -5,14 +5,13 @@ require 'spec_helper'
describe 'Set up Mattermost slash commands', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:service) { project.create_mattermost_slash_commands_service }
let(:mattermost_enabled) { true }
before do
stub_mattermost_setting(enabled: mattermost_enabled)
project.add_maintainer(user)
sign_in(user)
- visit edit_project_service_path(project, service)
+ visit edit_project_service_path(project, :mattermost_slash_commands)
end
describe 'user visits the mattermost slash command config page' do
@@ -30,6 +29,7 @@ describe 'Set up Mattermost slash commands', :js do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
+ find('input[name="service[active]"] + button').click
click_on 'Save changes'
expect(current_path).to eq(project_settings_integrations_path(project))
@@ -40,7 +40,6 @@ describe 'Set up Mattermost slash commands', :js do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
- check 'service_active'
click_on 'Save changes'
expect(current_path).to eq(project_settings_integrations_path(project))
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 752ef8d592d..4ce1acd9377 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -5,12 +5,11 @@ require 'spec_helper'
describe 'Slack slash commands' do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:service) { project.create_slack_slash_commands_service }
before do
project.add_maintainer(user)
sign_in(user)
- visit edit_project_service_path(project, service)
+ visit edit_project_service_path(project, :slack_slash_commands)
end
it 'shows a token placeholder' do
@@ -23,17 +22,17 @@ describe 'Slack slash commands' do
expect(page).to have_content('This service allows users to perform common')
end
- it 'redirects to the integrations page after saving but not activating' do
+ it 'redirects to the integrations page after saving but not activating', :js do
fill_in 'service_token', with: 'token'
+ find('input[name="service[active]"] + button').click
click_on 'Save'
expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
- it 'redirects to the integrations page after activating' do
+ it 'redirects to the integrations page after activating', :js do
fill_in 'service_token', with: 'token'
- check 'service_active'
click_on 'Save'
expect(current_path).to eq(project_settings_integrations_path(project))
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
index 2f6aad1d736..26734766ff0 100644
--- a/spec/features/projects/services/user_activates_youtrack_spec.rb
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -9,7 +9,7 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
def fill_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
index 638ecbcc11f..b460a031486 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
@@ -8,6 +8,7 @@ panel_groups:
type: "area-chart"
y_label: "y_label"
weight: 1
+ max_value: 1
metrics:
- id: metric_a1
query_range: 'query'
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
index fe2da16c9b7..20595cc0d73 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
@@ -11,6 +11,7 @@
"type": { "type": "string" },
"y_label": { "type": "string" },
"y_axis": { "$ref": "axis.json" },
+ "max_value": { "type": "number" },
"weight": { "type": "number" },
"metrics": {
"type": "array",
diff --git a/spec/frontend/integrations/edit/components/active_toggle_spec.js b/spec/frontend/integrations/edit/components/active_toggle_spec.js
new file mode 100644
index 00000000000..8a11c200c15
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/active_toggle_spec.js
@@ -0,0 +1,65 @@
+import { mount } from '@vue/test-utils';
+import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
+import { GlToggle } from '@gitlab/ui';
+
+const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
+
+describe('ActiveToggle', () => {
+ let wrapper;
+
+ const defaultProps = {
+ initialActivated: true,
+ disabled: false,
+ };
+
+ const createComponent = props => {
+ wrapper = mount(ActiveToggle, {
+ propsData: Object.assign({}, defaultProps, props),
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) wrapper.destroy();
+ });
+
+ const findGlToggle = () => wrapper.find(GlToggle);
+ const findButtonInToggle = () => findGlToggle().find('button');
+ const findInputInToggle = () => findGlToggle().find('input');
+
+ describe('template', () => {
+ describe('initialActivated is false', () => {
+ it('renders GlToggle as inactive', () => {
+ createComponent({
+ initialActivated: false,
+ });
+
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).not.toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('false');
+ });
+ });
+
+ describe('initialActivated is true', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders GlToggle as active', () => {
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('true');
+ });
+
+ describe('on toggle click', () => {
+ it('switches the form value', () => {
+ findButtonInToggle().trigger('click');
+
+ wrapper.vm.$nextTick(() => {
+ expect(findButtonInToggle().classes()).not.toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('false');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 642ab5138dc..d477de69716 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -16,7 +16,9 @@ describe('NoteHeader component', () => {
const findActionsWrapper = () => wrapper.find({ ref: 'discussionActions' });
const findChevronIcon = () => wrapper.find({ ref: 'chevronIcon' });
const findActionText = () => wrapper.find({ ref: 'actionText' });
+ const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
+ const findSpinner = () => wrapper.find({ ref: 'spinner' });
const author = {
avatar_url: null,
@@ -33,11 +35,7 @@ describe('NoteHeader component', () => {
store: new Vuex.Store({
actions,
}),
- propsData: {
- ...props,
- actionTextHtml: '',
- noteId: '1394',
- },
+ propsData: { ...props },
});
};
@@ -108,17 +106,18 @@ describe('NoteHeader component', () => {
createComponent();
expect(findActionText().exists()).toBe(false);
- expect(findTimestamp().exists()).toBe(false);
+ expect(findTimestampLink().exists()).toBe(false);
});
describe('when createdAt is passed as a prop', () => {
it('renders action text and a timestamp', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
+ noteId: 123,
});
expect(findActionText().exists()).toBe(true);
- expect(findTimestamp().exists()).toBe(true);
+ expect(findTimestampLink().exists()).toBe(true);
});
it('renders correct actionText if passed', () => {
@@ -133,8 +132,9 @@ describe('NoteHeader component', () => {
it('calls an action when timestamp is clicked', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
+ noteId: 123,
});
- findTimestamp().trigger('click');
+ findTimestampLink().trigger('click');
expect(actions.setTargetNoteHash).toHaveBeenCalled();
});
@@ -153,4 +153,30 @@ describe('NoteHeader component', () => {
expect(wrapper.find(GitlabTeamMemberBadge).exists()).toBe(expected);
},
);
+
+ describe('loading spinner', () => {
+ it('shows spinner when showSpinner is true', () => {
+ createComponent();
+ expect(findSpinner().exists()).toBe(true);
+ });
+
+ it('does not show spinner when showSpinner is false', () => {
+ createComponent({ showSpinner: false });
+ expect(findSpinner().exists()).toBe(false);
+ });
+ });
+
+ describe('timestamp', () => {
+ it('shows timestamp as a link if a noteId was provided', () => {
+ createComponent({ createdAt: new Date().toISOString(), noteId: 123 });
+ expect(findTimestampLink().exists()).toBe(true);
+ expect(findTimestamp().exists()).toBe(false);
+ });
+
+ it('shows timestamp as plain text if a noteId was not provided', () => {
+ createComponent({ createdAt: new Date().toISOString() });
+ expect(findTimestampLink().exists()).toBe(false);
+ expect(findTimestamp().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index 6fdf4f5cfb4..b5b431b5818 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -151,4 +151,35 @@ describe SnippetsHelper do
"<input type=\"text\" readonly=\"readonly\" class=\"js-snippet-url-area snippet-embed-input form-control\" data-url=\"#{url}\" value=\"<script src=&quot;#{url}.js&quot;></script>\" autocomplete=\"off\"></input>"
end
end
+
+ describe '#snippet_file_name' do
+ subject { helper.snippet_file_name(snippet) }
+
+ where(:snippet_type, :flag_enabled, :trait, :filename) do
+ [
+ [:personal_snippet, false, nil, 'foo.txt'],
+ [:personal_snippet, true, nil, 'foo.txt'],
+ [:personal_snippet, false, :repository, 'foo.txt'],
+ [:personal_snippet, true, :repository, '.gitattributes'],
+
+ [:project_snippet, false, nil, 'foo.txt'],
+ [:project_snippet, true, nil, 'foo.txt'],
+ [:project_snippet, false, :repository, 'foo.txt'],
+ [:project_snippet, true, :repository, '.gitattributes']
+ ]
+ end
+
+ with_them do
+ let(:snippet) { create(snippet_type, trait, file_name: 'foo.txt') }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(snippet.author)
+ stub_feature_flags(version_snippets: flag_enabled)
+ end
+
+ it 'returns the correct filename' do
+ expect(subject).to eq filename
+ end
+ end
+ end
end
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 82d1f815ca8..72d04be822f 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -23,9 +23,9 @@ describe('IntegrationSettingsForm', () => {
// Form Reference
expect(integrationSettingsForm.$form).toBeDefined();
expect(integrationSettingsForm.$form.prop('nodeName')).toEqual('FORM');
+ expect(integrationSettingsForm.formActive).toBeDefined();
// Form Child Elements
- expect(integrationSettingsForm.$serviceToggle).toBeDefined();
expect(integrationSettingsForm.$submitBtn).toBeDefined();
expect(integrationSettingsForm.$submitBtnLoader).toBeDefined();
expect(integrationSettingsForm.$submitBtnLabel).toBeDefined();
@@ -45,13 +45,15 @@ describe('IntegrationSettingsForm', () => {
});
it('should remove `novalidate` attribute to form when called with `true`', () => {
- integrationSettingsForm.toggleServiceState(true);
+ integrationSettingsForm.formActive = true;
+ integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.attr('novalidate')).not.toBeDefined();
});
it('should set `novalidate` attribute to form when called with `false`', () => {
- integrationSettingsForm.toggleServiceState(false);
+ integrationSettingsForm.formActive = false;
+ integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.attr('novalidate')).toBeDefined();
});
@@ -66,8 +68,9 @@ describe('IntegrationSettingsForm', () => {
it('should set Save button label to "Test settings and save changes" when serviceActive & canTestService are `true`', () => {
integrationSettingsForm.canTestService = true;
+ integrationSettingsForm.formActive = true;
- integrationSettingsForm.toggleSubmitBtnLabel(true);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual(
'Test settings and save changes',
@@ -76,18 +79,22 @@ describe('IntegrationSettingsForm', () => {
it('should set Save button label to "Save changes" when either serviceActive or canTestService (or both) is `false`', () => {
integrationSettingsForm.canTestService = false;
+ integrationSettingsForm.formActive = false;
- integrationSettingsForm.toggleSubmitBtnLabel(false);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
- integrationSettingsForm.toggleSubmitBtnLabel(true);
+ integrationSettingsForm.formActive = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
integrationSettingsForm.canTestService = true;
+ integrationSettingsForm.formActive = false;
- integrationSettingsForm.toggleSubmitBtnLabel(false);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 515d72add92..5d5e2fe2a33 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -58,6 +58,7 @@ notes:
- system_note_metadata
- note_diff_file
- suggestions
+- diff_note_positions
- review
label_links:
- target
@@ -134,6 +135,7 @@ merge_requests:
- pipelines_for_merge_request
- merge_request_assignees
- suggestions
+- diff_note_positions
- unresolved_notes
- assignees
- reviews
@@ -517,6 +519,8 @@ error_tracking_setting:
- project
suggestions:
- note
+diff_note_positions:
+- note
metrics_setting:
- project
protected_environments:
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 1eac580bc5e..80ae9a08257 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer, quarantine: { flaky: 'http
@project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
@shared = @project.import_export_shared
- allow(Feature).to receive(:enabled?).and_call_original
+ allow(Feature).to receive(:enabled?) { true }
stub_feature_flags(project_import_ndjson: ndjson_enabled)
setup_import_export_config('complex')
@@ -34,6 +34,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer, quarantine: { flaky: 'http
allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
+ expect(@shared).not_to receive(:error)
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index ded57b1d576..8adc360026d 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -29,12 +29,11 @@ describe Gitlab::ImportExport::Project::TreeSaver do
before_all do
RSpec::Mocks.with_temporary_scope do
- allow(Feature).to receive(:enabled?).and_call_original
+ allow(Feature).to receive(:enabled?) { true }
stub_feature_flags(project_export_as_ndjson: ndjson_enabled)
project.add_maintainer(user)
- stub_feature_flags(project_export_as_ndjson: ndjson_enabled)
project_tree_saver = described_class.new(project: project, current_user: user, shared: shared)
project_tree_saver.save
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 3cb02a8bcb3..b2fca0b5954 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -15,7 +15,8 @@ describe Gitlab::Metrics::Dashboard::Processor do
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter,
- Gitlab::Metrics::Dashboard::Stages::AlertsInserter
+ Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
+ Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter
]
end
@@ -28,6 +29,12 @@ describe Gitlab::Metrics::Dashboard::Processor do
end
end
+ it 'includes an id for each dashboard panel' do
+ expect(all_panels).to satisfy_all do |panel|
+ panel[:id].present?
+ end
+ end
+
it 'includes boolean to indicate if panel group has custom metrics' do
expect(dashboard[:panel_groups]).to all(include( { has_custom_metrics: boolean } ))
end
@@ -199,9 +206,11 @@ describe Gitlab::Metrics::Dashboard::Processor do
private
def all_metrics
- dashboard[:panel_groups].flat_map do |group|
- group[:panels].flat_map { |panel| panel[:metrics] }
- end
+ all_panels.flat_map { |panel| panel[:metrics] }
+ end
+
+ def all_panels
+ dashboard[:panel_groups].flat_map { |group| group[:panels] }
end
def get_metric_details(metric)
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
index 426a54bea78..6124f471e39 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
@@ -63,5 +63,24 @@ describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
)
end
end
+
+ context 'when dashboard panels has unknown schema attributes' do
+ before do
+ error = ActiveModel::UnknownAttributeError.new(double, 'unknown_panel_attribute')
+ allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_raise(error)
+ end
+
+ it 'no panel has assigned id' do
+ transform!
+
+ expect(fetch_panel_ids(dashboard)).to all be_nil
+ end
+
+ it 'logs the failure' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception)
+
+ transform!
+ end
+ end
end
end
diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb
index a00ba35feef..dedb8a8da4d 100644
--- a/spec/models/diff_note_position_spec.rb
+++ b/spec/models/diff_note_position_spec.rb
@@ -3,14 +3,35 @@
require 'spec_helper'
describe DiffNotePosition, type: :model do
- it 'has a position attribute' do
- diff_position = build(:diff_position)
- line_code = 'bd4b7bfff3a247ccf6e3371c41ec018a55230bcc_534_521'
- diff_note_position = build(:diff_note_position, line_code: line_code, position: diff_position)
-
- expect(diff_note_position.position).to eq(diff_position)
- expect(diff_note_position.line_code).to eq(line_code)
- expect(diff_note_position.diff_content_type).to eq('text')
+ describe '.create_or_update_by' do
+ context 'when a diff note' do
+ let(:note) { create(:diff_note_on_merge_request) }
+ let(:diff_position) { build(:diff_position) }
+ let(:line_code) { 'bd4b7bfff3a247ccf6e3371c41ec018a55230bcc_534_521' }
+ let(:diff_note_position) { note.diff_note_positions.first }
+ let(:params) { { diff_type: :head, line_code: line_code, position: diff_position } }
+
+ context 'does not have a diff note position' do
+ it 'creates a diff note position' do
+ described_class.create_or_update_for(note, params)
+
+ expect(diff_note_position.position).to eq(diff_position)
+ expect(diff_note_position.line_code).to eq(line_code)
+ expect(diff_note_position.diff_content_type).to eq('text')
+ end
+ end
+
+ context 'has a diff note position' do
+ it 'updates the existing diff note position' do
+ create(:diff_note_position, note: note)
+ described_class.create_or_update_for(note, params)
+
+ expect(note.diff_note_positions.size).to eq(1)
+ expect(diff_note_position.position).to eq(diff_position)
+ expect(diff_note_position.line_code).to eq(line_code)
+ end
+ end
+ end
end
it 'unique by note_id and diff type' do
diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb
new file mode 100644
index 00000000000..fced2eb7fce
--- /dev/null
+++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Discussions::CaptureDiffNotePositionService do
+ context 'image note on diff' do
+ let!(:note) { create(:image_diff_note_on_merge_request) }
+
+ subject { described_class.new(note.noteable, ['files/images/any_image.png']) }
+
+ it 'is note affected by the service' do
+ expect(Gitlab::Diff::PositionTracer).not_to receive(:new)
+
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
+ end
+
+ context 'when empty paths are passed as a param' do
+ let!(:note) { create(:diff_note_on_merge_request) }
+
+ subject { described_class.new(note.noteable, []) }
+
+ it 'does not calculate positons' do
+ expect(Gitlab::Diff::PositionTracer).not_to receive(:new)
+
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
+ end
+end
diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
new file mode 100644
index 00000000000..7b1e207f3eb
--- /dev/null
+++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Discussions::CaptureDiffNotePositionsService do
+ context 'when merge request has a discussion' do
+ let(:source_branch) { 'compare-with-merge-head-source' }
+ let(:target_branch) { 'compare-with-merge-head-target' }
+ let(:merge_request) { create(:merge_request, source_branch: source_branch, target_branch: target_branch) }
+ let(:project) { merge_request.project }
+
+ let(:offset) { 30 }
+ let(:first_new_line) { 508 }
+ let(:second_new_line) { 521 }
+
+ let(:service) { described_class.new(merge_request) }
+
+ def build_position(new_line, diff_refs)
+ path = 'files/markdown/ruby-style-guide.md'
+ Gitlab::Diff::Position.new(old_path: path, new_path: path,
+ new_line: new_line, diff_refs: diff_refs)
+ end
+
+ def note_for(new_line)
+ position = build_position(new_line, merge_request.diff_refs)
+ create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request)
+ end
+
+ def verify_diff_note_position!(note, line)
+ id, old_line, new_line = note.line_code.split('_')
+
+ expect(new_line).to eq(line.to_s)
+ expect(note.diff_note_positions.size).to eq(1)
+
+ diff_position = note.diff_note_positions.last
+ diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: merge_request.target_branch_sha,
+ start_sha: merge_request.target_branch_sha,
+ head_sha: merge_request.merge_ref_head.sha)
+
+ expect(diff_position.line_code).to eq("#{id}_#{old_line.to_i - offset}_#{new_line}")
+ expect(diff_position.position).to eq(build_position(new_line.to_i, diff_refs))
+ end
+
+ let!(:first_discussion_note) { note_for(first_new_line) }
+ let!(:second_discussion_note) { note_for(second_new_line) }
+ let!(:second_discussion_another_note) do
+ create(:diff_note_on_merge_request,
+ project: project,
+ position: second_discussion_note.position,
+ discussion_id: second_discussion_note.discussion_id,
+ noteable: merge_request)
+ end
+
+ context 'and position of the discussion changed on target branch head' do
+ it 'diff positions are created for the first notes of the discussions' do
+ MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ service.execute
+
+ verify_diff_note_position!(first_discussion_note, first_new_line)
+ verify_diff_note_position!(second_discussion_note, second_new_line)
+
+ expect(second_discussion_another_note.diff_note_positions).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 8f17e8083e3..45519ddf3d3 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -33,6 +33,24 @@ describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_sta
expect(merge_request.merge_status).to eq('can_be_merged')
end
+ it 'update diff discussion positions' do
+ expect_next_instance_of(Discussions::CaptureDiffNotePositionsService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject
+ end
+
+ context 'when merge_ref_head_comments is disabled' do
+ it 'does not update diff discussion positions' do
+ stub_feature_flags(merge_ref_head_comments: false)
+
+ expect(Discussions::CaptureDiffNotePositionsService).not_to receive(:new)
+
+ subject
+ end
+ end
+
it 'updates the merge ref' do
expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a03b78a9a7a..c461dd700ec 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -143,10 +143,21 @@ describe Notes::CreateService do
end
it 'note is associated with a note diff file' do
+ MergeRequests::MergeToRefService.new(merge_request.project, merge_request.author).execute(merge_request)
+
note = described_class.new(project_with_repo, user, new_opts).execute
expect(note).to be_persisted
expect(note.note_diff_file).to be_present
+ expect(note.diff_note_positions).to be_present
+ end
+
+ it 'does not create diff positions merge_ref_head_comments is disabled' do
+ stub_feature_flags(merge_ref_head_comments: false)
+
+ expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new)
+
+ described_class.new(project_with_repo, user, new_opts).execute
end
end
@@ -160,6 +171,8 @@ describe Notes::CreateService do
end
it 'note is not associated with a note diff file' do
+ expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new)
+
note = described_class.new(project_with_repo, user, new_opts).execute
expect(note).to be_persisted
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index fd41c5c8fe3..47d69ca1f6a 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -73,7 +73,9 @@ module TestEnv
'submodule_inside_folder' => 'b491b92',
'png-lfs' => 'fe42f41',
'sha-starting-with-large-number' => '8426165',
- 'invalid-utf8-diff-paths' => '99e4853'
+ 'invalid-utf8-diff-paths' => '99e4853',
+ 'compare-with-merge-head-source' => 'b5f4399',
+ 'compare-with-merge-head-target' => '2f1e176'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 4e287f36648..1a5668946c6 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -38,15 +38,12 @@ module ImportExport
end
def setup_reader(reader)
- case reader
- when :legacy_reader
- allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true)
- allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false)
- when :ndjson_reader
+ if reader == :ndjson_reader && Feature.enabled?(:project_import_ndjson)
allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(false)
allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(true)
else
- raise "invalid reader #{reader}. Supported readers: :legacy_reader, :ndjson_reader"
+ allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true)
+ allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false)
end
end