diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb index 07a648bb8c96ad5d542bc0ce21d0c971aaf9431d..f8855fb6debe19195ddc9f6773fee039acc48a2b 100644 --- a/app/services/jira_connect/sync_service.rb +++ b/app/services/jira_connect/sync_service.rb @@ -6,11 +6,11 @@ def initialize(project) self.project = project end - def execute(commits: nil, branches: nil, merge_requests: nil) + def execute(commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil) JiraConnectInstallation.for_project(project).each do |installation| client = Atlassian::JiraConnect::Client.new(installation.base_url, installation.shared_secret) - response = client.store_dev_info(project: project, commits: commits, branches: branches, merge_requests: merge_requests) + response = client.store_dev_info(project: project, commits: commits, branches: branches, merge_requests: merge_requests, update_sequence_id: update_sequence_id) log_response(response) end diff --git a/app/workers/jira_connect/sync_branch_worker.rb b/app/workers/jira_connect/sync_branch_worker.rb index 8c3416478fd15ced53ab66aff3439078ae88dff2..4c1c987353d6ec1ff721dfae7ceea428fe505281 100644 --- a/app/workers/jira_connect/sync_branch_worker.rb +++ b/app/workers/jira_connect/sync_branch_worker.rb @@ -8,7 +8,7 @@ class SyncBranchWorker # rubocop:disable Scalability/IdempotentWorker feature_category :integrations loggable_arguments 1, 2 - def perform(project_id, branch_name, commit_shas) + def perform(project_id, branch_name, commit_shas, update_sequence_id = nil) project = Project.find_by_id(project_id) return unless project @@ -16,7 +16,7 @@ def perform(project_id, branch_name, commit_shas) branches = [project.repository.find_branch(branch_name)] if branch_name.present? commits = project.commits_by(oids: commit_shas) if commit_shas.present? - JiraConnect::SyncService.new(project).execute(commits: commits, branches: branches) + JiraConnect::SyncService.new(project).execute(commits: commits, branches: branches, update_sequence_id: update_sequence_id) end end end diff --git a/app/workers/jira_connect/sync_merge_request_worker.rb b/app/workers/jira_connect/sync_merge_request_worker.rb index b78bb8dfe169a910a64d70e53ff5c3cfab2bb7c5..f45ab38f35de303ca996bb0b1009e6530c5d403e 100644 --- a/app/workers/jira_connect/sync_merge_request_worker.rb +++ b/app/workers/jira_connect/sync_merge_request_worker.rb @@ -7,12 +7,12 @@ class SyncMergeRequestWorker # rubocop:disable Scalability/IdempotentWorker queue_namespace :jira_connect feature_category :integrations - def perform(merge_request_id) + def perform(merge_request_id, update_sequence_id = nil) merge_request = MergeRequest.find_by_id(merge_request_id) return unless merge_request && merge_request.project - JiraConnect::SyncService.new(merge_request.project).execute(merge_requests: [merge_request]) + JiraConnect::SyncService.new(merge_request.project).execute(merge_requests: [merge_request], update_sequence_id: update_sequence_id) end end end diff --git a/lib/atlassian/jira_connect/client.rb b/lib/atlassian/jira_connect/client.rb index 0b578c03782bd422648f7774c5fd0ee9c7f44d73..d548251b60263a2a6aa39b769b1dc6d93b2b371e 100644 --- a/lib/atlassian/jira_connect/client.rb +++ b/lib/atlassian/jira_connect/client.rb @@ -3,19 +3,24 @@ module Atlassian module JiraConnect class Client < Gitlab::HTTP + def self.generate_update_sequence_id + Gitlab::Metrics::System.monotonic_time.to_i + end + def initialize(base_uri, shared_secret) @base_uri = base_uri @shared_secret = shared_secret end - def store_dev_info(project:, commits: nil, branches: nil, merge_requests: nil) + def store_dev_info(project:, commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil) dev_info_json = { repositories: [ Serializers::RepositoryEntity.represent( project, commits: commits, branches: branches, - merge_requests: merge_requests + merge_requests: merge_requests, + update_sequence_id: update_sequence_id ) ] }.to_json diff --git a/lib/atlassian/jira_connect/serializers/base_entity.rb b/lib/atlassian/jira_connect/serializers/base_entity.rb index c5490aa3f54419ef2e826f4fb9cf7329e96e2aa3..94deb174a45a9e1e8690ece10bab44d49877e71c 100644 --- a/lib/atlassian/jira_connect/serializers/base_entity.rb +++ b/lib/atlassian/jira_connect/serializers/base_entity.rb @@ -9,12 +9,12 @@ class BaseEntity < Grape::Entity format_with(:string) { |value| value.to_s } - expose :monotonic_time, as: :updateSequenceId + expose :update_sequence_id, as: :updateSequenceId private - def monotonic_time - Gitlab::Metrics::System.monotonic_time.to_i + def update_sequence_id + options[:update_sequence_id] || Client.generate_update_sequence_id end end end diff --git a/lib/atlassian/jira_connect/serializers/repository_entity.rb b/lib/atlassian/jira_connect/serializers/repository_entity.rb index 819ca2b62e098acb0180a42461cbf92f8b92beb0..9ae88ea21d163cc297b3a26b480f1c4a63ffadb5 100644 --- a/lib/atlassian/jira_connect/serializers/repository_entity.rb +++ b/lib/atlassian/jira_connect/serializers/repository_entity.rb @@ -15,13 +15,13 @@ class RepositoryEntity < BaseEntity end expose :commits do |project, options| - JiraConnect::Serializers::CommitEntity.represent options[:commits], project: project + JiraConnect::Serializers::CommitEntity.represent options[:commits], project: project, update_sequence_id: options[:update_sequence_id] end expose :branches do |project, options| - JiraConnect::Serializers::BranchEntity.represent options[:branches], project: project + JiraConnect::Serializers::BranchEntity.represent options[:branches], project: project, update_sequence_id: options[:update_sequence_id] end expose :pullRequests do |project, options| - JiraConnect::Serializers::PullRequestEntity.represent options[:merge_requests], project: project + JiraConnect::Serializers::PullRequestEntity.represent options[:merge_requests], project: project, update_sequence_id: options[:update_sequence_id] end end end diff --git a/spec/lib/atlassian/jira_connect/client_spec.rb b/spec/lib/atlassian/jira_connect/client_spec.rb index 40ffec21b269f00f57a181a9b050a40a3441c7a5..2fd2fe6617320dadc5493021765a7afff0691255 100644 --- a/spec/lib/atlassian/jira_connect/client_spec.rb +++ b/spec/lib/atlassian/jira_connect/client_spec.rb @@ -11,6 +11,14 @@ Timecop.freeze { example.run } end + describe '.generate_update_sequence_id' do + it 'returns monotonic_time converted it to integer' do + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(1.0) + + expect(described_class.generate_update_sequence_id).to eq(1) + end + end + describe '#store_dev_info' do it "calls the API with auth headers" do expected_jwt = Atlassian::Jwt.encode( diff --git a/spec/lib/atlassian/jira_connect/serializers/base_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/base_entity_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d7672c0baf1b702494f5c5894c80fbf82388a113 --- /dev/null +++ b/spec/lib/atlassian/jira_connect/serializers/base_entity_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Atlassian::JiraConnect::Serializers::BaseEntity do + let(:update_sequence_id) { nil } + + subject do + described_class.represent( + anything, + update_sequence_id: update_sequence_id + ) + end + + it 'generates the update_sequence_id' do + allow(Atlassian::JiraConnect::Client).to receive(:generate_update_sequence_id).and_return(1) + + expect(subject.value_for(:updateSequenceId)).to eq(1) + end + + context 'with update_sequence_id option' do + let(:update_sequence_id) { 123 } + + it 'uses the custom update_sequence_id' do + expect(subject.value_for(:updateSequenceId)).to eq(123) + end + end +end diff --git a/spec/lib/atlassian/jira_connect/serializers/repository_entity_spec.rb b/spec/lib/atlassian/jira_connect/serializers/repository_entity_spec.rb index 23ba1770827e20cd341756291609af99c3cb4a85..9100398ecc5156131539616bd7f5bc01ab580284 100644 --- a/spec/lib/atlassian/jira_connect/serializers/repository_entity_spec.rb +++ b/spec/lib/atlassian/jira_connect/serializers/repository_entity_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Atlassian::JiraConnect::Serializers::RepositoryEntity do + let(:update_sequence_id) { nil } + subject do project = create(:project, :repository) commits = [project.commit] @@ -13,9 +15,23 @@ project, commits: commits, branches: branches, - merge_requests: merge_requests + merge_requests: merge_requests, + update_sequence_id: update_sequence_id ).to_json end it { is_expected.to match_schema('jira_connect/repository') } + + context 'with custom update_sequence_id' do + let(:update_sequence_id) { 1.0 } + + it 'passes the update_sequence_id on to the nested entities', :aggregate_failures do + parsed_subject = Gitlab::Json.parse(subject) + + expect(parsed_subject['updateSequenceId']).to eq(update_sequence_id) + expect(parsed_subject['commits'].first['updateSequenceId']).to eq(update_sequence_id) + expect(parsed_subject['branches'].first['updateSequenceId']).to eq(update_sequence_id) + expect(parsed_subject['pullRequests'].first['updateSequenceId']).to eq(update_sequence_id) + end + end end diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb index e26ca30d0e12dafdadcdb45c399549ad8a4a0903..83088bb2e79f2152aea2b753f692210f830bd92c 100644 --- a/spec/services/jira_connect/sync_service_spec.rb +++ b/spec/services/jira_connect/sync_service_spec.rb @@ -23,7 +23,8 @@ def expect_jira_client_call(return_value = { 'status': 'success' }) project: project, commits: commits, branches: [instance_of(Gitlab::Git::Branch)], - merge_requests: merge_requests + merge_requests: merge_requests, + update_sequence_id: anything ).and_return(return_value) end end diff --git a/spec/workers/jira_connect/sync_branch_worker_spec.rb b/spec/workers/jira_connect/sync_branch_worker_spec.rb index 2da3ea9d256f4545041a4d78a89d0da1faf30ff9..4aa2f89de7bb44444eed8c6c32ac7f8dab83373f 100644 --- a/spec/workers/jira_connect/sync_branch_worker_spec.rb +++ b/spec/workers/jira_connect/sync_branch_worker_spec.rb @@ -4,7 +4,10 @@ RSpec.describe JiraConnect::SyncBranchWorker do describe '#perform' do - let_it_be(:project) { create(:project, :repository) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:subscription) { create(:jira_connect_subscription, installation: create(:jira_connect_installation), namespace: group) } + let(:project_id) { project.id } let(:branch_name) { 'master' } let(:commit_shas) { %w(b83d6e3 5a62481) } @@ -13,7 +16,7 @@ def expect_jira_sync_service_execute(args) expect_next_instance_of(JiraConnect::SyncService) do |instance| - expect(instance).to receive(:execute).with(args) + expect(instance).to receive(:execute).with(args.merge(update_sequence_id: nil)) end end @@ -61,5 +64,31 @@ def expect_jira_sync_service_execute(args) subject end end + + context 'with update_sequence_id' do + let(:update_sequence_id) { 1 } + let(:request_url) { 'https://sample.atlassian.net/rest/devinfo/0.10/bulk' } + let(:request_body) do + { + repositories: [ + Atlassian::JiraConnect::Serializers::RepositoryEntity.represent( + project, + commits: project.commits_by(oids: commit_shas), + branches: [project.repository.find_branch(branch_name)], + update_sequence_id: update_sequence_id + ) + ] + }.to_json + end + + subject { described_class.new.perform(project_id, branch_name, commit_shas, update_sequence_id) } + + it 'sends the reqeust with custom update_sequence_id' do + expect(Atlassian::JiraConnect::Client).to receive(:post) + .with(URI(request_url), headers: anything, body: request_body) + + subject + end + end end end diff --git a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb index 764201e750af2d19f36397c8451039a1ce7e98f4..b3c0db4f260746741676ad9b5b316c1d77183c8e 100644 --- a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb +++ b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb @@ -4,14 +4,18 @@ RSpec.describe JiraConnect::SyncMergeRequestWorker do describe '#perform' do - let(:merge_request) { create(:merge_request) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:subscription) { create(:jira_connect_subscription, installation: create(:jira_connect_installation), namespace: group) } + let_it_be(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request_id) { merge_request.id } subject { described_class.new.perform(merge_request_id) } it 'calls JiraConnect::SyncService#execute' do expect_next_instance_of(JiraConnect::SyncService) do |service| - expect(service).to receive(:execute).with(merge_requests: [merge_request]) + expect(service).to receive(:execute).with(merge_requests: [merge_request], update_sequence_id: nil) end subject @@ -26,5 +30,30 @@ subject end end + + context 'with update_sequence_id' do + let(:update_sequence_id) { 1 } + let(:request_url) { 'https://sample.atlassian.net/rest/devinfo/0.10/bulk' } + let(:request_body) do + { + repositories: [ + Atlassian::JiraConnect::Serializers::RepositoryEntity.represent( + project, + merge_requests: [merge_request], + update_sequence_id: update_sequence_id + ) + ] + }.to_json + end + + subject { described_class.new.perform(merge_request_id, update_sequence_id) } + + it 'sends the request with custom update_sequence_id' do + expect(Atlassian::JiraConnect::Client).to receive(:post) + .with(URI(request_url), headers: anything, body: request_body) + + subject + end + end end end