summaryrefslogtreecommitdiff
path: root/app
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 /app
parent221b529789f4090341a825695aeb49b8df6dd11d (diff)
downloadgitlab-ce-b7c735c8ac11b8182807070fc6f84f2606e15427.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-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
19 files changed, 260 insertions, 27 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.