Refactor resolvability checks based on type
This commit is contained in:
parent
64c1735c22
commit
cc656a1199
|
@ -292,17 +292,6 @@ module Issuable
|
||||||
self.class.to_ability_name
|
self.class.to_ability_name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Convert this Issuable class name to a format usable by notifications.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# issuable.class # => MergeRequest
|
|
||||||
# issuable.human_class_name # => "merge request"
|
|
||||||
|
|
||||||
def human_class_name
|
|
||||||
@human_class_name ||= self.class.name.titleize.downcase
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a Hash of attributes to be used for Twitter card metadata
|
# Returns a Hash of attributes to be used for Twitter card metadata
|
||||||
def card_attributes
|
def card_attributes
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,29 @@
|
||||||
module Noteable
|
module Noteable
|
||||||
|
# Names of all implementers of `Noteable` that support resolvable notes.
|
||||||
|
RESOLVABLE_TYPES = %w(MergeRequest).freeze
|
||||||
|
|
||||||
|
def base_class_name
|
||||||
|
self.class.base_class.name
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convert this Noteable class name to a format usable by notifications.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# noteable.class # => MergeRequest
|
||||||
|
# noteable.human_class_name # => "merge request"
|
||||||
|
def human_class_name
|
||||||
|
@human_class_name ||= base_class_name.titleize.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def supports_resolvable_notes?
|
||||||
|
RESOLVABLE_TYPES.include?(base_class_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def supports_discussions?
|
||||||
|
DiscussionNote::NOTEABLE_TYPES.include?(base_class_name)
|
||||||
|
end
|
||||||
|
|
||||||
def discussion_notes
|
def discussion_notes
|
||||||
notes
|
notes
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,8 @@ module ResolvableDiscussion
|
||||||
:last_note
|
:last_note
|
||||||
)
|
)
|
||||||
|
|
||||||
|
delegate :potentially_resolvable?, to: :first_note
|
||||||
|
|
||||||
delegate :resolved_at,
|
delegate :resolved_at,
|
||||||
:resolved_by,
|
:resolved_by,
|
||||||
|
|
||||||
|
@ -27,11 +29,6 @@ module ResolvableDiscussion
|
||||||
allow_nil: true
|
allow_nil: true
|
||||||
end
|
end
|
||||||
|
|
||||||
# Keep this method in sync with the `potentially_resolvable` scope on `ResolvableNote`
|
|
||||||
def potentially_resolvable?
|
|
||||||
for_merge_request?
|
|
||||||
end
|
|
||||||
|
|
||||||
def resolvable?
|
def resolvable?
|
||||||
return @resolvable if @resolvable.present?
|
return @resolvable if @resolvable.present?
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
module ResolvableNote
|
module ResolvableNote
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# Names of all subclasses of `Note` that can be resolvable.
|
||||||
RESOLVABLE_TYPES = %w(DiffNote DiscussionNote).freeze
|
RESOLVABLE_TYPES = %w(DiffNote DiscussionNote).freeze
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
@ -8,10 +9,8 @@ module ResolvableNote
|
||||||
|
|
||||||
validates :resolved_by, presence: true, if: :resolved?
|
validates :resolved_by, presence: true, if: :resolved?
|
||||||
|
|
||||||
# Keep this scope in sync with the logic in `#potentially_resolvable?` in subclasses of `Discussion` that are resolvable.
|
# Keep this scope in sync with `#potentially_resolvable?`
|
||||||
# `RESOLVABLE_TYPES` should include names of all subclasses that are resolvable (where the method can return true), and
|
scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: Noteable::RESOLVABLE_TYPES) }
|
||||||
# the scope should also match the criteria `ResolvableDiscussion#potentially_resolvable?` puts on resolvability.
|
|
||||||
scope :potentially_resolvable, -> { where(type: RESOLVABLE_TYPES).where(noteable_type: 'MergeRequest') }
|
|
||||||
# Keep this scope in sync with `#resolvable?`
|
# Keep this scope in sync with `#resolvable?`
|
||||||
scope :resolvable, -> { potentially_resolvable.user }
|
scope :resolvable, -> { potentially_resolvable.user }
|
||||||
|
|
||||||
|
@ -31,7 +30,10 @@ module ResolvableNote
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
delegate :potentially_resolvable?, to: :to_discussion
|
# Keep this method in sync with the `potentially_resolvable` scope
|
||||||
|
def potentially_resolvable?
|
||||||
|
RESOLVABLE_TYPES.include?(self.class.name) && noteable.supports_resolvable_notes?
|
||||||
|
end
|
||||||
|
|
||||||
# Keep this method in sync with the `resolvable` scope
|
# Keep this method in sync with the `resolvable` scope
|
||||||
def resolvable?
|
def resolvable?
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
# A discussion on merge request or commit diffs consisting of `DiffNote` notes.
|
# A discussion on merge request or commit diffs consisting of `DiffNote` notes.
|
||||||
|
#
|
||||||
|
# A discussion of this type can be resolvable.
|
||||||
class DiffDiscussion < Discussion
|
class DiffDiscussion < Discussion
|
||||||
include DiscussionOnDiff
|
include DiscussionOnDiff
|
||||||
|
|
||||||
|
def self.note_class
|
||||||
|
DiffNote
|
||||||
|
end
|
||||||
|
|
||||||
delegate :position,
|
delegate :position,
|
||||||
:original_position,
|
:original_position,
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# A note on merge request or commit diffs
|
# A note on merge request or commit diffs
|
||||||
|
#
|
||||||
|
# A note of this type can be resolvable.
|
||||||
class DiffNote < Note
|
class DiffNote < Note
|
||||||
include NoteOnDiff
|
include NoteOnDiff
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# A non-diff discussion on an issue, merge request, commit, or snippet, consisting of `DiscussionNote` notes.
|
# A non-diff discussion on an issue, merge request, commit, or snippet, consisting of `DiscussionNote` notes.
|
||||||
|
#
|
||||||
|
# A discussion of this type can be resolvable.
|
||||||
class Discussion
|
class Discussion
|
||||||
include ResolvableDiscussion
|
include ResolvableDiscussion
|
||||||
|
|
||||||
|
@ -54,6 +56,10 @@ class Discussion
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.note_class
|
||||||
|
DiscussionNote
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(notes, noteable = nil)
|
def initialize(notes, noteable = nil)
|
||||||
@notes = notes
|
@notes = notes
|
||||||
@noteable = noteable
|
@noteable = noteable
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# A note in a non-diff discussion on an issue, merge request, commit, or snippet
|
# A note in a non-diff discussion on an issue, merge request, commit, or snippet.
|
||||||
|
#
|
||||||
|
# A note of this type can be resolvable.
|
||||||
class DiscussionNote < Note
|
class DiscussionNote < Note
|
||||||
|
# Names of all implementers of `Noteable` that support discussions.
|
||||||
NOTEABLE_TYPES = %w(MergeRequest Issue Commit Snippet).freeze
|
NOTEABLE_TYPES = %w(MergeRequest Issue Commit Snippet).freeze
|
||||||
RESOLVABLE_TYPES = %w(MergeRequest).freeze
|
|
||||||
|
|
||||||
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
|
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
# A discussion to wrap a single `Note` note on the root of an issue, merge request,
|
# A discussion to wrap a single `Note` note on the root of an issue, merge request,
|
||||||
# commit, or snippet, that is not displayed as a discussion.
|
# commit, or snippet, that is not displayed as a discussion.
|
||||||
|
#
|
||||||
|
# A discussion of this type is never resolvable.
|
||||||
class IndividualNoteDiscussion < Discussion
|
class IndividualNoteDiscussion < Discussion
|
||||||
def potentially_resolvable?
|
def self.note_class
|
||||||
false
|
Note
|
||||||
end
|
end
|
||||||
|
|
||||||
def individual_note?
|
def individual_note?
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# A discussion on merge request or commit diffs consisting of `LegacyDiffNote` notes.
|
# A discussion on merge request or commit diffs consisting of `LegacyDiffNote` notes.
|
||||||
|
#
|
||||||
# All new diff discussions are of the type `DiffDiscussion`, but any diff discussions created
|
# All new diff discussions are of the type `DiffDiscussion`, but any diff discussions created
|
||||||
# before the introduction of the new implementation still use `LegacyDiffDiscussion`.
|
# before the introduction of the new implementation still use `LegacyDiffDiscussion`.
|
||||||
|
#
|
||||||
|
# A discussion of this type is never resolvable.
|
||||||
class LegacyDiffDiscussion < Discussion
|
class LegacyDiffDiscussion < Discussion
|
||||||
include DiscussionOnDiff
|
include DiscussionOnDiff
|
||||||
|
|
||||||
|
@ -8,8 +11,8 @@ class LegacyDiffDiscussion < Discussion
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def potentially_resolvable?
|
def self.note_class
|
||||||
false
|
LegacyDiffNote
|
||||||
end
|
end
|
||||||
|
|
||||||
def collapsed?
|
def collapsed?
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# A note on merge request or commit diffs, using the legacy implementation.
|
# A note on merge request or commit diffs, using the legacy implementation.
|
||||||
|
#
|
||||||
# All new diff notes are of the type `DiffNote`, but any diff notes created
|
# All new diff notes are of the type `DiffNote`, but any diff notes created
|
||||||
# before the introduction of the new implementation still use `LegacyDiffNote`.
|
# before the introduction of the new implementation still use `LegacyDiffNote`.
|
||||||
|
#
|
||||||
|
# A note of this type is never resolvable.
|
||||||
class LegacyDiffNote < Note
|
class LegacyDiffNote < Note
|
||||||
include NoteOnDiff
|
include NoteOnDiff
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# A note on the root of an issue, merge request, commit, or snippet.
|
||||||
|
#
|
||||||
|
# A note of this type is never resolvable.
|
||||||
class Note < ActiveRecord::Base
|
class Note < ActiveRecord::Base
|
||||||
extend ActiveModel::Naming
|
extend ActiveModel::Naming
|
||||||
include Gitlab::CurrentSettings
|
include Gitlab::CurrentSettings
|
||||||
|
@ -225,11 +228,7 @@ class Note < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_be_discussion_note?
|
def can_be_discussion_note?
|
||||||
DiscussionNote::NOTEABLE_TYPES.include?(self.noteable_type) && !part_of_discussion?
|
self.noteable.supports_discussions? && !part_of_discussion?
|
||||||
end
|
|
||||||
|
|
||||||
def can_be_resolvable?
|
|
||||||
DiscussionNote::RESOLVABLE_TYPES.include?(self.noteable_type)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def discussion_class(noteable = nil)
|
def discussion_class(noteable = nil)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
# contains that commit, they are displayed as if they were a discussion.
|
# contains that commit, they are displayed as if they were a discussion.
|
||||||
#
|
#
|
||||||
# This represents one of those discussions, consisting of `Note` notes.
|
# This represents one of those discussions, consisting of `Note` notes.
|
||||||
|
#
|
||||||
|
# A discussion of this type is never resolvable.
|
||||||
class OutOfContextDiscussion < Discussion
|
class OutOfContextDiscussion < Discussion
|
||||||
# Returns an array of discussion ID components
|
# Returns an array of discussion ID components
|
||||||
def self.build_discussion_id(note)
|
def self.build_discussion_id(note)
|
||||||
|
@ -13,8 +15,8 @@ class OutOfContextDiscussion < Discussion
|
||||||
def self.override_discussion_id(note)
|
def self.override_discussion_id(note)
|
||||||
discussion_id(note)
|
discussion_id(note)
|
||||||
end
|
end
|
||||||
|
|
||||||
def potentially_resolvable?
|
def self.note_class
|
||||||
false
|
Note
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,12 +36,14 @@ module Issues
|
||||||
|
|
||||||
def item_for_discussion(discussion)
|
def item_for_discussion(discussion)
|
||||||
first_note_to_resolve = discussion.first_note_to_resolve || discussion.first_note
|
first_note_to_resolve = discussion.first_note_to_resolve || discussion.first_note
|
||||||
other_note_count = discussion.notes.size - 1
|
|
||||||
note_url = Gitlab::UrlBuilder.build(first_note_to_resolve)
|
|
||||||
|
|
||||||
is_very_first_note = first_note_to_resolve == discussion.first_note
|
is_very_first_note = first_note_to_resolve == discussion.first_note
|
||||||
action = is_very_first_note ? "started" : "commented on"
|
action = is_very_first_note ? "started" : "commented on"
|
||||||
|
|
||||||
|
note_url = Gitlab::UrlBuilder.build(first_note_to_resolve)
|
||||||
|
|
||||||
|
other_note_count = discussion.notes.size - 1
|
||||||
|
|
||||||
discussion_info = "- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): "
|
discussion_info = "- [ ] #{first_note_to_resolve.author.to_reference} #{action} a [discussion](#{note_url}): "
|
||||||
discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0
|
discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
- noteable_name = @note.noteable.human_class_name
|
||||||
|
|
||||||
|
.pull-left.btn-group.append-right-10.comment-type-dropdown.js-comment-type-dropdown
|
||||||
|
%input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
|
||||||
|
|
||||||
|
- if @note.can_be_discussion_note?
|
||||||
|
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
|
||||||
|
= icon('caret-down')
|
||||||
|
|
||||||
|
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
|
||||||
|
%li#comment.droplab-item-selected{ data: { value: '', 'button-text' => 'Comment', 'secondary-button-text' => "Comment & close #{noteable_name}" } }
|
||||||
|
= icon('check')
|
||||||
|
.description
|
||||||
|
%strong Comment
|
||||||
|
%p
|
||||||
|
Add a general comment to this #{noteable_name}.
|
||||||
|
|
||||||
|
%li.divider
|
||||||
|
|
||||||
|
%li#discussion{ data: { value: 'DiscussionNote', 'button-text' => 'Start discussion', 'secondary-button-text' => "Start discussion & close #{noteable_name}" } }
|
||||||
|
= icon('check')
|
||||||
|
.description
|
||||||
|
%strong Start discussion
|
||||||
|
%p
|
||||||
|
= succeed '.' do
|
||||||
|
Discuss a specific suggestion or question
|
||||||
|
- if @note.noteable.supports_resolvable_notes?
|
||||||
|
that needs to be resolved
|
|
@ -1,28 +0,0 @@
|
||||||
- noteable_type = @note.noteable_type
|
|
||||||
|
|
||||||
.pull-left.btn-group.append-right-10.comment-type-dropdown.js-comment-type-dropdown
|
|
||||||
%input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
|
|
||||||
- if @note.can_be_discussion_note?
|
|
||||||
= button_tag type: 'button', class: 'btn btn-nr dropdown-toggle comment-btn js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => 'Open comment type dropdown' do
|
|
||||||
= icon('caret-down')
|
|
||||||
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
|
|
||||||
%li#comment.droplab-item-selected{ data: { value: '', 'button-text' => 'Comment', 'secondary-button-text' => "Comment & close #{noteable_type.titleize.downcase}" } }
|
|
||||||
= icon('check')
|
|
||||||
.description
|
|
||||||
%strong Comment
|
|
||||||
%p= "Add a general comment to this #{noteable_type.titleize.downcase}."
|
|
||||||
%li.divider
|
|
||||||
- if @note.can_be_resolvable?
|
|
||||||
%li#discussion{ data: { value: 'DiscussionNote', 'button-text' => 'Start discussion', 'secondary-button-text' => "Start discussion & close #{noteable_type.titleize.downcase}" } }
|
|
||||||
= icon('check')
|
|
||||||
.description
|
|
||||||
%strong Start discussion
|
|
||||||
%p
|
|
||||||
Discuss a specific suggestion or question that needs to be resolved.
|
|
||||||
- else
|
|
||||||
%li#discussion{ data: { value: 'DiscussionNote', 'button-text' => 'Start discussion', 'secondary-button-text' => "Start discussion & close #{noteable_type.titleize.downcase}"} }
|
|
||||||
= icon('check')
|
|
||||||
.description
|
|
||||||
%strong Start discussion
|
|
||||||
%p
|
|
||||||
Discuss a specific suggestion or question.
|
|
|
@ -28,7 +28,7 @@
|
||||||
.error-alert
|
.error-alert
|
||||||
|
|
||||||
.note-form-actions.clearfix
|
.note-form-actions.clearfix
|
||||||
= render partial: 'projects/notes/comment_type_button', locals: { show_close_button: true }
|
= render partial: 'projects/notes/comment_button'
|
||||||
|
|
||||||
= yield(:note_actions)
|
= yield(:note_actions)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue