diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-15 12:09:18 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-15 12:09:18 +0000 |
commit | b7c735c8ac11b8182807070fc6f84f2606e15427 (patch) | |
tree | e74b4d25abb8bbf23546f001dd94515e2840a3a3 /app | |
parent | 221b529789f4090341a825695aeb49b8df6dd11d (diff) | |
download | gitlab-ce-b7c735c8ac11b8182807070fc6f84f2606e15427.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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. |