Use NotesFinder in module IssuableActions
Add spec for concern IssuableActions Add shared samples for discussions endpoint Add schema validations for discussions Fix rubocop style issue Make target assignable Use new possibility to provide target
This commit is contained in:
parent
08c03e7e17
commit
355a2df560
|
@ -98,11 +98,10 @@ module IssuableActions
|
|||
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
# rubocop:disable CodeReuse/ActiveRecord
|
||||
def discussions
|
||||
notes = issuable.discussion_notes
|
||||
notes = NotesFinder.new(project, current_user, finder_params_for_issuable).execute
|
||||
.inc_relations_for_view
|
||||
.with_notes_filter(notes_filter)
|
||||
.includes(:noteable)
|
||||
.fresh
|
||||
|
||||
|
@ -117,7 +116,7 @@ module IssuableActions
|
|||
|
||||
render json: discussion_serializer.represent(discussions, context: self)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
# rubocop:enable CodeReuse/ActiveRecord
|
||||
|
||||
private
|
||||
|
||||
|
@ -222,4 +221,13 @@ module IssuableActions
|
|||
def parent
|
||||
@project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def finder_params_for_issuable
|
||||
{
|
||||
target: @issuable,
|
||||
notes_filter: notes_filter
|
||||
}
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class NotesFinder
|
||||
FETCH_OVERLAP = 5.seconds
|
||||
|
||||
attr_reader :target_type
|
||||
|
||||
# Used to filter Notes
|
||||
# When used with target_type and target_id this returns notes specifically for the controller
|
||||
#
|
||||
|
@ -10,6 +12,7 @@ class NotesFinder
|
|||
# current_user - which user check authorizations with
|
||||
# project - which project to look for notes on
|
||||
# params:
|
||||
# target: noteable
|
||||
# target_type: string
|
||||
# target_id: integer
|
||||
# last_fetched_at: time
|
||||
|
@ -18,7 +21,8 @@ class NotesFinder
|
|||
def initialize(project, current_user, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
@params = params.dup
|
||||
@target_type = @params[:target_type]
|
||||
end
|
||||
|
||||
def execute
|
||||
|
@ -32,7 +36,27 @@ class NotesFinder
|
|||
def target
|
||||
return @target if defined?(@target)
|
||||
|
||||
target_type = @params[:target_type]
|
||||
if target_given?
|
||||
use_explicit_target
|
||||
else
|
||||
find_target_by_type_and_ids
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target_given?
|
||||
@params.key?(:target)
|
||||
end
|
||||
|
||||
def use_explicit_target
|
||||
@target = @params[:target]
|
||||
@target_type = @target.class.name.underscore
|
||||
|
||||
@target
|
||||
end
|
||||
|
||||
def find_target_by_type_and_ids
|
||||
target_id = @params[:target_id]
|
||||
target_iid = @params[:target_iid]
|
||||
|
||||
|
@ -45,13 +69,11 @@ class NotesFinder
|
|||
@project.commit(target_id)
|
||||
end
|
||||
else
|
||||
noteables_for_type_by_id(target_type, target_id, target_iid)
|
||||
noteable_for_type_by_id(target_type, target_id, target_iid)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def noteables_for_type_by_id(type, id, iid)
|
||||
def noteable_for_type_by_id(type, id, iid)
|
||||
query = if id
|
||||
{ id: id }
|
||||
else
|
||||
|
@ -77,10 +99,6 @@ class NotesFinder
|
|||
search(notes)
|
||||
end
|
||||
|
||||
def target_type
|
||||
@params[:target_type]
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def notes_of_any_type
|
||||
types = %w(commit issue merge_request snippet)
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe IssuableActions do
|
||||
let(:project) { double('project') }
|
||||
let(:user) { double('user') }
|
||||
let(:issuable) { double('issuable') }
|
||||
let(:finder_params_for_issuable) { {} }
|
||||
let(:notes_result) { double('notes_result') }
|
||||
let(:discussion_serializer) { double('discussion_serializer') }
|
||||
|
||||
let(:controller) do
|
||||
klass = Class.new do
|
||||
attr_reader :current_user, :project, :issuable
|
||||
|
||||
def self.before_action(action, params = nil)
|
||||
end
|
||||
|
||||
include IssuableActions
|
||||
|
||||
def initialize(issuable, project, user, finder_params)
|
||||
@issuable = issuable
|
||||
@project = project
|
||||
@current_user = user
|
||||
@finder_params = finder_params
|
||||
end
|
||||
|
||||
def finder_params_for_issuable
|
||||
@finder_params
|
||||
end
|
||||
|
||||
def params
|
||||
{
|
||||
notes_filter: 1
|
||||
}
|
||||
end
|
||||
|
||||
def prepare_notes_for_rendering(notes)
|
||||
[]
|
||||
end
|
||||
|
||||
def render(options)
|
||||
end
|
||||
end
|
||||
|
||||
klass.new(issuable, project, user, finder_params_for_issuable)
|
||||
end
|
||||
|
||||
describe '#discussions' do
|
||||
before do
|
||||
allow(user).to receive(:set_notes_filter)
|
||||
allow(user).to receive(:user_preference)
|
||||
allow(discussion_serializer).to receive(:represent)
|
||||
end
|
||||
|
||||
it 'instantiates and calls NotesFinder as expected' do
|
||||
expect(Discussion).to receive(:build_collection).and_return([])
|
||||
expect(DiscussionSerializer).to receive(:new).and_return(discussion_serializer)
|
||||
expect(NotesFinder).to receive(:new).with(project, user, finder_params_for_issuable).and_call_original
|
||||
|
||||
expect_any_instance_of(NotesFinder).to receive(:execute).and_return(notes_result)
|
||||
|
||||
expect(notes_result).to receive_messages(inc_relations_for_view: notes_result, includes: notes_result, fresh: notes_result)
|
||||
|
||||
controller.discussions
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1260,6 +1260,28 @@ describe Projects::IssuesController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
context do
|
||||
it_behaves_like 'discussions provider' do
|
||||
let!(:author) { create(:user) }
|
||||
let!(:project) { create(:project) }
|
||||
|
||||
let!(:issue) { create(:issue, project: project, author: user) }
|
||||
|
||||
let!(:note_on_issue1) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
|
||||
let!(:note_on_issue2) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
|
||||
|
||||
let(:requested_iid) { issue.iid }
|
||||
let(:expected_discussion_count) { 3 }
|
||||
let(:expected_discussion_ids) do
|
||||
[
|
||||
issue.notes.first.discussion_id,
|
||||
note_on_issue1.discussion_id,
|
||||
note_on_issue2.discussion_id
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns discussion json' do
|
||||
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
|
||||
|
||||
|
|
|
@ -1121,6 +1121,22 @@ describe Projects::MergeRequestsController do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context do
|
||||
it_behaves_like 'discussions provider' do
|
||||
let!(:author) { create(:user) }
|
||||
let!(:project) { create(:project) }
|
||||
|
||||
let!(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
let!(:mr_note1) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
|
||||
let!(:mr_note2) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
|
||||
|
||||
let(:requested_iid) { merge_request.iid }
|
||||
let(:expected_discussion_count) { 2 }
|
||||
let(:expected_discussion_ids) { [mr_note1.discussion_id, mr_note2.discussion_id] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET edit' do
|
||||
|
|
|
@ -118,16 +118,11 @@ describe NotesFinder do
|
|||
|
||||
context 'for target' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:note1) { create :note_on_commit, project: project }
|
||||
let(:note2) { create :note_on_commit, project: project }
|
||||
let!(:note1) { create :note_on_commit, project: project }
|
||||
let!(:note2) { create :note_on_commit, project: project }
|
||||
let(:commit) { note1.noteable }
|
||||
let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
|
||||
|
||||
before do
|
||||
note1
|
||||
note2
|
||||
end
|
||||
|
||||
it 'finds all notes' do
|
||||
notes = described_class.new(project, user, params).execute
|
||||
expect(notes.size).to eq(2)
|
||||
|
@ -194,6 +189,28 @@ describe NotesFinder do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for explicit target' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let!(:note1) { create :note_on_commit, project: project, created_at: 1.day.ago, updated_at: 2.hours.ago }
|
||||
let!(:note2) { create :note_on_commit, project: project }
|
||||
let(:commit) { note1.noteable }
|
||||
let(:params) { { target: commit } }
|
||||
|
||||
it 'returns the expected notes' do
|
||||
expect(described_class.new(project, user, params).execute).to eq([note1, note2])
|
||||
end
|
||||
|
||||
it 'returns the expected notes when last_fetched_at is given' do
|
||||
params = { target: commit, last_fetched_at: 1.hour.ago.to_i }
|
||||
expect(described_class.new(project, user, params).execute).to eq([note2])
|
||||
end
|
||||
|
||||
it 'fails when nil is provided' do
|
||||
params = { target: nil }
|
||||
expect { described_class.new(project, user, params).execute }.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.search' do
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"type": "object",
|
||||
"required" : [
|
||||
"id",
|
||||
"notes",
|
||||
"individual_note"
|
||||
],
|
||||
"properties" : {
|
||||
"id": { "type": "string" },
|
||||
"individual_note": { "type": "boolean" },
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties" : {
|
||||
"id": { "type": "string" },
|
||||
"type": { "type": ["string", "null"] },
|
||||
"body": { "type": "string" },
|
||||
"attachment": { "type": ["string", "null"]},
|
||||
"award_emoji": { "type": "array" },
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"id": { "type": "integer" },
|
||||
"state": { "type": "string" },
|
||||
"avatar_url": { "type": "uri" },
|
||||
"web_url": { "type": "uri" },
|
||||
"status_tooltip_html": { "type": ["string", "null"] },
|
||||
"path": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"created_at": { "type": "date" },
|
||||
"updated_at": { "type": "date" },
|
||||
"system": { "type": "boolean" },
|
||||
"noteable_id": { "type": "integer" },
|
||||
"noteable_iid": { "type": "integer" },
|
||||
"noteable_type": { "type": "string" },
|
||||
"resolved": { "type": "boolean" },
|
||||
"resolvable": { "type": "boolean" },
|
||||
"resolved_by": { "type": ["string", "null"] },
|
||||
"note": { "type": "string" },
|
||||
"note_html": { "type": "string" },
|
||||
"current_user": { "type": "object" },
|
||||
"suggestions": { "type": "array" },
|
||||
"discussion_id": { "type": "string" },
|
||||
"emoji_awardable": { "type": "boolean" },
|
||||
"report_abuse_path": { "type": "string" },
|
||||
"noteable_note_url": { "type": "string" },
|
||||
"resolve_path": { "type": "string" },
|
||||
"resolve_with_issue_path": { "type": "string" },
|
||||
"cached_markdown_version": { "type": "integer" },
|
||||
"human_access": { "type": ["string", "null"] },
|
||||
"toggle_award_path": { "type": "string" },
|
||||
"path": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"id", "attachment", "author", "created_at", "updated_at",
|
||||
"system", "noteable_id", "noteable_type"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"type": "array",
|
||||
"items": { "$ref": "discussion.json" }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
shared_examples 'discussions provider' do
|
||||
it 'returns the expected discussions' do
|
||||
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: requested_iid }
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(response).to match_response_schema('entities/discussions')
|
||||
|
||||
expect(json_response.size).to eq(expected_discussion_count)
|
||||
expect(json_response.pluck('id')).to eq(expected_discussion_ids)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue