diff --git a/lib/atlassian/jira_connect/client.rb b/lib/atlassian/jira_connect/client.rb index b8aa2cc8ea03c0c1ba08ec69913809cb7c4420e4..d38516e5288a905a1ec95cff187a3bd05ea47bea 100644 --- a/lib/atlassian/jira_connect/client.rb +++ b/lib/atlassian/jira_connect/client.rb @@ -14,14 +14,14 @@ def initialize(base_uri, shared_secret) def send_info(project:, update_sequence_id: nil, **args) common = { project: project, update_sequence_id: update_sequence_id } - dev_info = args.slice(:commits, :branches, :merge_requests) + dev_info = DevInfo.new(**common.merge(args.slice(:commits, :branches, :merge_requests))) build_info = args.slice(:pipelines) deploy_info = args.slice(:deployments) ff_info = args.slice(:feature_flags) responses = [] - responses << store_dev_info(**common, **dev_info) if dev_info.present? + responses << store_dev_info(dev_info) if dev_info.present? responses << store_build_info(**common, **build_info) if build_info.present? responses << store_deploy_info(**common, **deploy_info) if deploy_info.present? responses << store_ff_info(**common, **ff_info) if ff_info.present? @@ -93,17 +93,8 @@ def store_build_info(project:, pipelines:, update_sequence_id: nil) handle_response(r, 'builds') { |data| errors(data, 'rejectedBuilds') } end - def store_dev_info(project:, commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil) - repo = ::Atlassian::JiraConnect::Serializers::RepositoryEntity.represent( - project, - commits: commits, - branches: branches, - merge_requests: merge_requests, - user_notes_count: user_notes_count(merge_requests), - update_sequence_id: update_sequence_id - ) - - post('/rest/devinfo/0.10/bulk', { repositories: [repo] }) + def store_dev_info(dev_info) + post(dev_info.url, dev_info.body) end def post(path, payload) @@ -157,14 +148,6 @@ def errors(data, key) { 'errorMessages' => messages } end - def user_notes_count(merge_requests) - return unless merge_requests - - Note.count_for_collection(merge_requests.map(&:id), 'MergeRequest').to_h do |count_group| - [count_group.noteable_id, count_group.count] - end - end - def jwt_token(http_method, uri) claims = Atlassian::Jwt.build_claims( Atlassian::JiraConnect.app_key, diff --git a/lib/atlassian/jira_connect/dev_info.rb b/lib/atlassian/jira_connect/dev_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..90ccc1939d259b76646be394f84e970738d672cb --- /dev/null +++ b/lib/atlassian/jira_connect/dev_info.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Atlassian + module JiraConnect + class DevInfo + URL = '/rest/devinfo/0.10/bulk' + + def initialize(project:, commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil) + @project = project + @commits = commits + @branches = branches + @merge_requests = merge_requests + @update_sequence_id = update_sequence_id + end + + def url + URL + end + + def body + repo = ::Atlassian::JiraConnect::Serializers::RepositoryEntity.represent( + @project, + commits: @commits, + branches: @branches, + merge_requests: @merge_requests, + user_notes_count: user_notes_count, + update_sequence_id: @update_sequence_id + ) + + { repositories: [repo] } + end + + def present? + [@commits, @branches, @merge_requests].any?(&:present?) + end + + private + + def user_notes_count + return unless @merge_requests + + Note.count_for_collection(@merge_requests.map(&:id), 'MergeRequest').to_h do |count_group| + [count_group.noteable_id, count_group.count] + end + end + end + end +end diff --git a/spec/fixtures/api/schemas/jira_connect/dev_info.json b/spec/fixtures/api/schemas/jira_connect/dev_info.json new file mode 100644 index 0000000000000000000000000000000000000000..98437353fde35b460feff5ac39e65179a86da298 --- /dev/null +++ b/spec/fixtures/api/schemas/jira_connect/dev_info.json @@ -0,0 +1,8 @@ +{ + "repositories": { + "type": "array", + "items": { + "$ref": "./repository.json" + } + } +} diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb index dd3130c78bfe6af1d3104ae5ac3e96df85c6e083..1857a1431dfe621d937c7fabc4995d48631cbf80 100644 --- a/spec/lib/atlassian/jira_connect/client_spec.rb +++ b/spec/lib/atlassian/jira_connect/client_spec.rb @@ -58,12 +58,16 @@ deployments: :q ).and_return(:deploys_stored) - expect(subject).to receive(:store_dev_info).with( + expect(Atlassian::JiraConnect::DevInfo).to receive(:new).with( project: project, update_sequence_id: :x, commits: :a, branches: :b, merge_requests: :c + ).and_call_original + + expect(subject).to receive(:store_dev_info).with( + instance_of(Atlassian::JiraConnect::DevInfo) ).and_return(:dev_stored) args = { @@ -83,9 +87,7 @@ it 'only calls methods that we need to call' do expect(subject).to receive(:store_dev_info).with( - project: project, - update_sequence_id: :x, - commits: :a + instance_of(Atlassian::JiraConnect::DevInfo) ).and_return(:dev_stored) args = { @@ -402,15 +404,7 @@ def expected_headers(path) end it "calls the API with auth headers" do - subject.send(:store_dev_info, project: project) - end - - it 'avoids N+1 database queries' do - control_count = ActiveRecord::QueryRecorder.new { subject.send(:store_dev_info, project: project, merge_requests: merge_requests) }.count - - merge_requests << create(:merge_request, :unique_branches) - - expect { subject.send(:store_dev_info, project: project, merge_requests: merge_requests) }.not_to exceed_query_limit(control_count) + subject.send(:store_dev_info, Atlassian::JiraConnect::DevInfo.new(project: project)) end end end diff --git a/spec/lib/atlassian/jira_connect/dev_info_spec.rb b/spec/lib/atlassian/jira_connect/dev_info_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..357168a94b95d506b1ec06d87f05f8d7495bba66 --- /dev/null +++ b/spec/lib/atlassian/jira_connect/dev_info_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Atlassian::JiraConnect::DevInfo do + let_it_be(:project) { create_default(:project, :repository).freeze } + + let(:update_sequence_id) { '123' } + + describe '#url' do + subject { described_class.new(project: project).url } + + it { is_expected.to eq('/rest/devinfo/0.10/bulk') } + end + + describe '#body' do + let_it_be(:merge_request) { create(:merge_request, :unique_branches, title: 'TEST-123') } + let_it_be(:note) { create(:note, noteable: merge_request, project: merge_request.project) } + let_it_be(:branches) do + project.repository.create_branch('TEST-123', project.default_branch_or_main) + [project.repository.find_branch('TEST-123')] + end + + let(:merge_requests) { [merge_request] } + + subject(:body) { described_class.new(project: project, branches: branches, merge_requests: merge_requests, update_sequence_id: update_sequence_id).body.to_json } + + it 'matches the schema' do + expect(body).to match_schema('jira_connect/dev_info') + end + + it 'avoids N+1 database queries' do + control_count = ActiveRecord::QueryRecorder.new { subject }.count + + merge_requests << create(:merge_request, :unique_branches) + + expect { subject }.not_to exceed_query_limit(control_count) + end + end + + describe '#present?' do + let(:arguments) { { commits: nil, branches: nil, merge_requests: nil } } + + subject { described_class.new(**{ project: project, update_sequence_id: update_sequence_id }.merge(arguments)).present? } + + it { is_expected.to eq(false) } + + context 'with commits, branches or merge requests' do + let(:arguments) { { commits: anything, branches: anything, merge_requests: anything } } + + it { is_expected.to eq(true) } + end + end +end