diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-26 21:08:20 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-26 21:08:20 +0300 |
commit | 6ee98e127334fd235f251c4a4a76a396f301ee77 (patch) | |
tree | 7c66eb437214b600ee7abc7a73d10160283c3030 /app | |
parent | c9d79ef3b5b67792e331a4cc8e6325f3b4a04760 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
22 files changed, 405 insertions, 36 deletions
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index dcc0fa501a3..a4ca2a6c857 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -8,8 +8,9 @@ import identicon from '../../vue_shared/components/identicon.vue'; import loadingButton from '../../vue_shared/components/loading_button.vue'; import UninstallApplicationButton from './uninstall_application_button.vue'; import UninstallApplicationConfirmationModal from './uninstall_application_confirmation_modal.vue'; +import UpdateApplicationConfirmationModal from './update_application_confirmation_modal.vue'; -import { APPLICATION_STATUS } from '../constants'; +import { APPLICATION_STATUS, ELASTIC_STACK } from '../constants'; export default { components: { @@ -18,6 +19,7 @@ export default { GlLink, UninstallApplicationButton, UninstallApplicationConfirmationModal, + UpdateApplicationConfirmationModal, }, directives: { GlModalDirective, @@ -233,6 +235,17 @@ export default { return label; }, + updatingNeedsConfirmation() { + if (this.version) { + const majorVersion = parseInt(this.version.split('.')[0], 10); + + if (!Number.isNaN(majorVersion)) { + return this.id === ELASTIC_STACK && majorVersion < 3; + } + } + + return false; + }, isUpdating() { // Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend return this.status === APPLICATION_STATUS.UPDATING; @@ -248,6 +261,12 @@ export default { title: this.title, }); }, + updateModalId() { + return `update-${this.id}`; + }, + uninstallModalId() { + return `uninstall-${this.id}`; + }, }, watch: { updateSuccessful(updateSuccessful) { @@ -268,7 +287,7 @@ export default { params: this.installApplicationRequestParams, }); }, - updateClicked() { + updateConfirmed() { eventHub.$emit('updateApplication', { id: this.id, params: this.installApplicationRequestParams, @@ -356,14 +375,36 @@ export default { > {{ updateFailureDescription }} </div> - <loading-button - v-if="updateAvailable || updateFailed || isUpdating" - class="btn btn-primary js-cluster-application-update-button mt-2" - :loading="isUpdating" - :disabled="isUpdating" - :label="updateButtonLabel" - @click="updateClicked" - /> + <template v-if="updateAvailable || updateFailed || isUpdating"> + <template v-if="updatingNeedsConfirmation"> + <loading-button + v-gl-modal-directive="updateModalId" + class="btn btn-primary js-cluster-application-update-button mt-2" + :loading="isUpdating" + :disabled="isUpdating" + :label="updateButtonLabel" + data-qa-selector="update_button_with_confirmation" + :data-qa-application="id" + /> + + <update-application-confirmation-modal + :application="id" + :application-title="title" + @confirm="updateConfirmed()" + /> + </template> + + <loading-button + v-else + class="btn btn-primary js-cluster-application-update-button mt-2" + :loading="isUpdating" + :disabled="isUpdating" + :label="updateButtonLabel" + data-qa-selector="update_button" + :data-qa-application="id" + @click="updateConfirmed" + /> + </template> </div> </div> <div @@ -389,7 +430,7 @@ export default { /> <uninstall-application-button v-if="displayUninstallButton" - v-gl-modal-directive="'uninstall-' + id" + v-gl-modal-directive="uninstallModalId" :status="status" data-qa-selector="uninstall_button" :data-qa-application="id" diff --git a/app/assets/javascripts/clusters/components/update_application_confirmation_modal.vue b/app/assets/javascripts/clusters/components/update_application_confirmation_modal.vue new file mode 100644 index 00000000000..04aa28e9b74 --- /dev/null +++ b/app/assets/javascripts/clusters/components/update_application_confirmation_modal.vue @@ -0,0 +1,65 @@ +<script> +import { GlModal } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; +import { ELASTIC_STACK } from '../constants'; + +const CUSTOM_APP_WARNING_TEXT = { + [ELASTIC_STACK]: s__( + 'ClusterIntegration|Your Elasticsearch cluster will be re-created during this upgrade. Your logs will be re-indexed, and you will lose historical logs from hosts terminated in the last 30 days.', + ), +}; + +export default { + components: { + GlModal, + }, + props: { + application: { + type: String, + required: true, + }, + applicationTitle: { + type: String, + required: true, + }, + }, + computed: { + title() { + return sprintf(s__('ClusterIntegration|Update %{appTitle}'), { + appTitle: this.applicationTitle, + }); + }, + warningText() { + return sprintf( + s__('ClusterIntegration|You are about to update %{appTitle} on your cluster.'), + { + appTitle: this.applicationTitle, + }, + ); + }, + customAppWarningText() { + return CUSTOM_APP_WARNING_TEXT[this.application]; + }, + modalId() { + return `update-${this.application}`; + }, + }, + methods: { + confirmUpdate() { + this.$emit('confirm'); + }, + }, +}; +</script> +<template> + <gl-modal + ok-variant="danger" + cancel-variant="light" + :ok-title="title" + :modal-id="modalId" + :title="title" + @ok="confirmUpdate()" + > + {{ warningText }} <span v-html="customAppWarningText"></span> + </gl-modal> +</template> diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb index f425582da4d..1b5d42ffcc7 100644 --- a/app/controllers/projects/alert_management_controller.rb +++ b/app/controllers/projects/alert_management_controller.rb @@ -3,7 +3,7 @@ class Projects::AlertManagementController < Projects::ApplicationController before_action :authorize_read_alert_management_alert! before_action do - push_frontend_feature_flag(:alert_management_create_alert_issue) + push_frontend_feature_flag(:alert_management_create_alert_issue, project) push_frontend_feature_flag(:alert_assignee, project) end diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb new file mode 100644 index 00000000000..58ad05b5365 --- /dev/null +++ b/app/graphql/types/release_assets_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + class ReleaseAssetsType < BaseObject + graphql_name 'ReleaseAssets' + + authorize :read_release + + alias_method :release, :object + + present_using ReleasePresenter + + field :assets_count, GraphQL::INT_TYPE, null: true, + description: 'Number of assets of the release' + field :links, Types::ReleaseLinkType.connection_type, null: true, + description: 'Asset links of the release' + field :sources, Types::ReleaseSourceType.connection_type, null: true, + description: 'Sources of the release' + end +end diff --git a/app/graphql/types/release_link_type.rb b/app/graphql/types/release_link_type.rb new file mode 100644 index 00000000000..c7a1d3f3767 --- /dev/null +++ b/app/graphql/types/release_link_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + class ReleaseLinkType < BaseObject + graphql_name 'ReleaseLink' + + authorize :read_release + + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the link' + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the link' + field :url, GraphQL::STRING_TYPE, null: true, + description: 'URL of the link' + + field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?, + description: 'Indicates the link points to an external resource' + end +end diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb new file mode 100644 index 00000000000..0ec1ad85a39 --- /dev/null +++ b/app/graphql/types/release_source_type.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + class ReleaseSourceType < BaseObject + graphql_name 'ReleaseSource' + + authorize :read_release_sources + + field :format, GraphQL::STRING_TYPE, null: true, + description: 'Format of the source' + field :url, GraphQL::STRING_TYPE, null: true, + description: 'Download URL of the source' + end +end diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index 632351be5d3..30c496f646d 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -23,6 +23,8 @@ module Types description: 'Timestamp of when the release was created' field :released_at, Types::TimeType, null: true, description: 'Timestamp of when the release was released' + field :assets, Types::ReleaseAssetsType, null: true, method: :itself, + description: 'Assets of the release' field :milestones, Types::MilestoneType.connection_type, null: true, description: 'Milestones associated to the release' diff --git a/app/models/draft_note.rb b/app/models/draft_note.rb new file mode 100644 index 00000000000..febede9beba --- /dev/null +++ b/app/models/draft_note.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true +class DraftNote < ApplicationRecord + include DiffPositionableNote + include Gitlab::Utils::StrongMemoize + include Sortable + include ShaAttribute + + PUBLISH_ATTRS = %i(noteable_id noteable_type type note).freeze + DIFF_ATTRS = %i(position original_position change_position commit_id).freeze + + sha_attribute :commit_id + + # Attribute used to store quick actions changes and users referenced. + attr_accessor :commands_changes + attr_accessor :users_referenced + + # Text with quick actions filtered out + attr_accessor :rendered_note + + attr_accessor :review + + belongs_to :author, class_name: 'User' + belongs_to :merge_request + + validates :merge_request_id, presence: true + validates :author_id, presence: true, uniqueness: { scope: [:merge_request_id, :discussion_id] }, if: :discussion_id? + validates :discussion_id, allow_nil: true, format: { with: /\A\h{40}\z/ } + + scope :authored_by, ->(u) { where(author_id: u.id) } + + delegate :file_path, :file_hash, :file_identifier_hash, to: :diff_file, allow_nil: true + + def self.positions + where.not(position: nil) + .select(:position) + .map(&:position) + end + + def project + merge_request.target_project + end + + # noteable_id and noteable_type methods + # are used to generate discussion_id on Discussion.discussion_id + def noteable_id + merge_request_id + end + + def noteable + merge_request + end + + def noteable_type + "MergeRequest" + end + + def for_commit? + commit_id.present? + end + + def importing? + false + end + + def resolvable? + false + end + + def emoji_awardable? + false + end + + def on_diff? + position&.complete? + end + + def type + return 'DiffNote' if on_diff? + return 'DiscussionNote' if discussion_id.present? + + 'Note' + end + + def references + { + users: users_referenced, + commands: commands_changes + } + end + + def line_code + @line_code ||= diff_file&.line_code_for_position(original_position) + end + + def publish_params + attrs = PUBLISH_ATTRS.dup + attrs.concat(DIFF_ATTRS) if on_diff? + params = slice(*attrs) + params[:in_reply_to_discussion_id] = discussion_id if discussion_id.present? + params[:review_id] = review.id if review.present? + + params + end + + def self.preload_author(draft_notes) + ActiveRecord::Associations::Preloader.new.preload(draft_notes, { author: :status }) + end + + def diff_file + strong_memoize(:diff_file) do + file = original_position&.diff_file(project.repository) + + file&.unfold_diff_lines(original_position) + + file + end + end + + def commit + @commit ||= project.commit(commit_id) if commit_id.present? + end +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b4d0b729454..8c3e042cd10 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -88,6 +88,9 @@ class MergeRequest < ApplicationRecord has_many :deployments, through: :deployment_merge_requests + has_many :draft_notes + has_many :reviews, inverse_of: :merge_request + KNOWN_MERGE_PARAMS = [ :auto_merge_strategy, :should_remove_source_branch, @@ -541,13 +544,21 @@ class MergeRequest < ApplicationRecord merge_request_diffs.where.not(id: merge_request_diff.id) end - # Overwritten in EE - def note_positions_for_paths(paths, _user = nil) + def note_positions_for_paths(paths, user = nil) positions = notes.new_diff_notes.joins(:note_diff_file) .where('note_diff_files.old_path IN (?) OR note_diff_files.new_path IN (?)', paths, paths) .positions - Gitlab::Diff::PositionCollection.new(positions, diff_head_sha) + collection = Gitlab::Diff::PositionCollection.new(positions, diff_head_sha) + + return collection unless user + + positions = draft_notes + .authored_by(user) + .positions + .select { |pos| paths.include?(pos.file_path) } + + collection.concat(positions) end def preloads_discussion_diff_highlighting? diff --git a/app/models/note.rb b/app/models/note.rb index d174ba8fe83..ec1a59ca383 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -72,6 +72,7 @@ class Note < ApplicationRecord belongs_to :author, class_name: "User" belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' + belongs_to :review, inverse_of: :notes has_many :todos diff --git a/app/models/project.rb b/app/models/project.rb index 99ac8a08ae3..b63096adae7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -329,6 +329,7 @@ class Project < ApplicationRecord has_many :repository_storage_moves, class_name: 'ProjectRepositoryStorageMove' has_many :webide_pipelines, -> { webide_source }, class_name: 'Ci::Pipeline', inverse_of: :project + has_many :reviews, inverse_of: :project accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 00000000000..5a30e2963c8 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Review < ApplicationRecord + include Participable + include Mentionable + + belongs_to :author, class_name: 'User', foreign_key: :author_id, inverse_of: :reviews + belongs_to :merge_request, inverse_of: :reviews + belongs_to :project, inverse_of: :reviews + + has_many :notes, -> { order(:id) }, inverse_of: :review + + delegate :name, to: :author, prefix: true + + participant :author + + def all_references(current_user = nil, extractor: nil) + ext = super + + notes.each do |note| + note.all_references(current_user, extractor: ext) + end + + ext + end + + def user_mentions + merge_request.user_mentions.where.not(note_id: nil) + end +end diff --git a/app/models/snippet_input_action.rb b/app/models/snippet_input_action.rb index 50de1b0c04e..64276085682 100644 --- a/app/models/snippet_input_action.rb +++ b/app/models/snippet_input_action.rb @@ -28,9 +28,17 @@ class SnippetInputAction def to_commit_action { action: action&.to_sym, - previous_path: previous_path, + previous_path: build_previous_path, file_path: file_path, content: content } end + + private + + def build_previous_path + return previous_path unless update_action? + + previous_path.presence || file_path + end end diff --git a/app/models/snippet_input_action_collection.rb b/app/models/snippet_input_action_collection.rb index bf20bce5b0a..56c32c376c4 100644 --- a/app/models/snippet_input_action_collection.rb +++ b/app/models/snippet_input_action_collection.rb @@ -5,7 +5,7 @@ class SnippetInputActionCollection attr_reader :actions - delegate :empty?, to: :actions + delegate :empty?, :any?, :[], to: :actions def initialize(actions = []) @actions = actions.map { |action| SnippetInputAction.new(action) } diff --git a/app/models/user.rb b/app/models/user.rb index f6d945101b6..6f58dcbda68 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -180,6 +180,8 @@ class User < ApplicationRecord has_one :user_highest_role has_one :user_canonical_email + has_many :reviews, foreign_key: :author_id, inverse_of: :author + # # Validations # diff --git a/app/policies/releases/link_policy.rb b/app/policies/releases/link_policy.rb new file mode 100644 index 00000000000..4a662fafb2f --- /dev/null +++ b/app/policies/releases/link_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Releases + class LinkPolicy < BasePolicy + delegate { @subject.release.project } + end +end diff --git a/app/policies/releases/source_policy.rb b/app/policies/releases/source_policy.rb new file mode 100644 index 00000000000..8b86b925589 --- /dev/null +++ b/app/policies/releases/source_policy.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Releases + class SourcePolicy < BasePolicy + delegate { @subject.project } + + rule { can?(:public_access) | can?(:reporter_access) }.policy do + enable :read_release_sources + end + + rule { ~can?(:read_release) }.prevent :read_release_sources + end +end diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb index ea46f0a234b..7b0a3d1e7b9 100644 --- a/app/presenters/release_presenter.rb +++ b/app/presenters/release_presenter.rb @@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated presents :release - delegate :project, :tag, to: :release + delegate :project, :tag, :assets_count, to: :release def commit_path return unless release.commit && can_download_code? diff --git a/app/services/snippets/base_service.rb b/app/services/snippets/base_service.rb index 60ba9ff6683..5d1fe815d83 100644 --- a/app/services/snippets/base_service.rb +++ b/app/services/snippets/base_service.rb @@ -72,11 +72,11 @@ module Snippets message end - def files_to_commit - snippet_files.to_commit_actions.presence || build_actions_from_params + def files_to_commit(snippet) + snippet_files.to_commit_actions.presence || build_actions_from_params(snippet) end - def build_actions_from_params + def build_actions_from_params(snippet) raise NotImplementedError end end diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb index b3af2d91e5c..434541a9097 100644 --- a/app/services/snippets/create_service.rb +++ b/app/services/snippets/create_service.rb @@ -43,9 +43,7 @@ module Snippets def create_params return params if snippet_files.empty? - first_file = snippet_files.actions.first - - params.merge(content: first_file.content, file_name: first_file.file_path) + params.merge(content: snippet_files[0].content, file_name: snippet_files[0].file_path) end def save_and_commit @@ -88,7 +86,7 @@ module Snippets message: 'Initial commit' } - @snippet.snippet_repository.multi_files_action(current_user, files_to_commit, commit_attrs) + @snippet.snippet_repository.multi_files_action(current_user, files_to_commit(@snippet), commit_attrs) end def move_temporary_files @@ -99,7 +97,7 @@ module Snippets end end - def build_actions_from_params + def build_actions_from_params(_snippet) [{ file_path: params[:file_name], content: params[:content] }] end end diff --git a/app/services/snippets/update_service.rb b/app/services/snippets/update_service.rb index 45b047af3d0..211ebb836cb 100644 --- a/app/services/snippets/update_service.rb +++ b/app/services/snippets/update_service.rb @@ -7,11 +7,13 @@ module Snippets UpdateError = Class.new(StandardError) def execute(snippet) + return invalid_params_error(snippet) unless valid_params? + if visibility_changed?(snippet) && !visibility_allowed?(snippet, visibility_level) return forbidden_visibility_error(snippet) end - snippet.assign_attributes(params) + update_snippet_attributes(snippet) spam_check(snippet, current_user) if save_and_commit(snippet) @@ -29,6 +31,19 @@ module Snippets visibility_level && visibility_level.to_i != snippet.visibility_level end + def update_snippet_attributes(snippet) + # We can remove the following condition once + # https://gitlab.com/gitlab-org/gitlab/-/issues/217801 + # is implemented. + # Once we can perform different operations through this service + # we won't need to keep track of the `content` and `file_name` fields + if snippet_files.any? + params.merge!(content: snippet_files[0].content, file_name: snippet_files[0].file_path) + end + + snippet.assign_attributes(params) + end + def save_and_commit(snippet) return false unless snippet.save @@ -81,13 +96,7 @@ module Snippets message: 'Update snippet' } - snippet.snippet_repository.multi_files_action(current_user, snippet_files(snippet), commit_attrs) - end - - def snippet_files(snippet) - [{ previous_path: snippet.file_name_on_repo, - file_path: params[:file_name], - content: params[:content] }] + snippet.snippet_repository.multi_files_action(current_user, files_to_commit(snippet), commit_attrs) end # Because we are removing repositories we don't want to remove @@ -99,7 +108,13 @@ module Snippets end def committable_attributes? - (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present? + (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present? || snippet_files.any? + end + + def build_actions_from_params(snippet) + [{ previous_path: snippet.file_name_on_repo, + file_path: params[:file_name], + content: params[:content] }] end end end diff --git a/app/views/users/_deletion_guidance.html.haml b/app/views/users/_deletion_guidance.html.haml index 7169aebea74..0024801dbf6 100644 --- a/app/views/users/_deletion_guidance.html.haml +++ b/app/views/users/_deletion_guidance.html.haml @@ -8,4 +8,4 @@ - personal_projects_count = user.personal_projects.count - unless personal_projects_count.zero? %li - = n_('personal project will be removed and cannot be restored', '%d personal projects will be removed and cannot be restored', personal_projects_count) + = n_('%d personal project will be removed and cannot be restored.', '%d personal projects will be removed and cannot be restored.', personal_projects_count) % personal_projects_count |