Commit 827fab55 authored by alexkalderimis's avatar alexkalderimis Committed by Alex Kalderimis

Add handlers for Jira connect error responses

This ensures that we gracefully handle the error responses for the
new JiraConnect synchronization endpoints.
parent 60ad6272
......@@ -31,7 +31,7 @@ module JiraConnect
jira_response: response&.to_json
}
if response && (response['errorMessages'] || response['rejectedBuilds'].present?)
if response && response['errorMessages']
logger.error(message)
else
logger.info(message)
......
......@@ -35,33 +35,41 @@ module Atlassian
def store_ff_info(project:, feature_flags:, **opts)
return unless Feature.enabled?(:jira_sync_feature_flags, project)
items = feature_flags.map { |flag| Serializers::FeatureFlagEntity.represent(flag, opts) }
items = feature_flags.map { |flag| ::Atlassian::JiraConnect::Serializers::FeatureFlagEntity.represent(flag, opts) }
items.reject! { |item| item.issue_keys.empty? }
return if items.empty?
post('/rest/featureflags/0.1/bulk', {
r = post('/rest/featureflags/0.1/bulk', {
flags: items,
properties: { projectId: "project-#{project.id}" }
})
if r['failedFeatureFlags'].present?
errors = r['failedFeatureFlags'].flat_map do |k, errs|
errs.map { |e| "#{k}: #{e['message']}" }
end
{ 'errorMessages' => errors }
end
end
def store_deploy_info(project:, deployments:, **opts)
return unless Feature.enabled?(:jira_sync_deployments, project)
items = deployments.map { |d| Serializers::DeploymentEntity.represent(d, opts) }
items = deployments.map { |d| ::Atlassian::JiraConnect::Serializers::DeploymentEntity.represent(d, opts) }
items.reject! { |d| d.issue_keys.empty? }
return if items.empty?
post('/rest/deployments/0.1/bulk', { deployments: items })
r = post('/rest/deployments/0.1/bulk', { deployments: items })
errors(r, 'rejectedDeployments')
end
def store_build_info(project:, pipelines:, update_sequence_id: nil)
return unless Feature.enabled?(:jira_sync_builds, project)
builds = pipelines.map do |pipeline|
build = Serializers::BuildEntity.represent(
build = ::Atlassian::JiraConnect::Serializers::BuildEntity.represent(
pipeline,
update_sequence_id: update_sequence_id
)
......@@ -71,7 +79,8 @@ module Atlassian
end.compact
return if builds.empty?
post('/rest/builds/0.1/bulk', { builds: builds })
r = post('/rest/builds/0.1/bulk', { builds: builds })
errors(r, 'rejectedBuilds')
end
def store_dev_info(project:, commits: nil, branches: nil, merge_requests: nil, update_sequence_id: nil)
......@@ -104,6 +113,19 @@ module Atlassian
{ providerMetadata: { product: "GitLab #{Gitlab::VERSION}" } }
end
def errors(response, key)
data = response.parsed_response
messages = if data[key].present?
data[key].flat_map do |rejection|
rejection['errors'].map { |e| e['message'] }
end
else
[]
end
{ 'errorMessages' => messages }
end
def user_notes_count(merge_requests)
return unless merge_requests
......
......@@ -126,10 +126,20 @@ RSpec.describe Atlassian::JiraConnect::Client do
->(text) { matcher.matches?(text) }
end
let(:rejections) { [] }
let(:response_body) do
{
acceptedDeployments: [],
rejectedDeployments: rejections,
unknownIssueKeys: []
}.to_json
end
before do
path = '/rest/deployments/0.1/bulk'
stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
.with(body: body, headers: expected_headers(path))
.to_return(body: response_body, headers: { 'Content-Type': 'application/json' })
end
it "calls the API with auth headers" do
......@@ -137,7 +147,7 @@ RSpec.describe Atlassian::JiraConnect::Client do
end
it 'only sends information about relevant MRs' do
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: have_attributes(size: 6) })
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: have_attributes(size: 6) }).and_call_original
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
......@@ -148,6 +158,18 @@ RSpec.describe Atlassian::JiraConnect::Client do
subject.send(:store_deploy_info, project: project, deployments: deployments.take(1))
end
context 'there are errors' do
let(:rejections) do
[{ errors: [{ message: 'X' }, { message: 'Y' }] }, { errors: [{ message: 'Z' }] }]
end
it 'reports the errors' do
response = subject.send(:store_deploy_info, project: project, deployments: deployments)
expect(response['errorMessages']).to eq(%w(X Y Z))
end
end
it 'does not call the API if the feature flag is not enabled' do
stub_feature_flags(jira_sync_deployments: false)
......@@ -159,7 +181,7 @@ RSpec.describe Atlassian::JiraConnect::Client do
it 'does call the API if the feature flag enabled for the project' do
stub_feature_flags(jira_sync_deployments: project)
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: Array })
expect(subject).to receive(:post).with('/rest/deployments/0.1/bulk', { deployments: Array }).and_call_original
subject.send(:store_deploy_info, project: project, deployments: deployments)
end
......@@ -178,12 +200,22 @@ RSpec.describe Atlassian::JiraConnect::Client do
->(text) { matcher.matches?(text) }
end
let(:failures) { {} }
let(:response_body) do
{
acceptedFeatureFlags: [],
failedFeatureFlags: failures,
unknownIssueKeys: []
}.to_json
end
before do
feature_flags.first.update!(description: 'RELEVANT-123')
feature_flags.second.update!(description: 'RELEVANT-123')
path = '/rest/featureflags/0.1/bulk'
stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
.with(body: body, headers: expected_headers(path))
.to_return(body: response_body, headers: { 'Content-Type': 'application/json' })
end
it "calls the API with auth headers" do
......@@ -193,7 +225,7 @@ RSpec.describe Atlassian::JiraConnect::Client do
it 'only sends information about relevant MRs' do
expect(subject).to receive(:post).with('/rest/featureflags/0.1/bulk', {
flags: have_attributes(size: 2), properties: Hash
})
}).and_call_original
subject.send(:store_ff_info, project: project, feature_flags: feature_flags)
end
......@@ -204,6 +236,21 @@ RSpec.describe Atlassian::JiraConnect::Client do
subject.send(:store_ff_info, project: project, feature_flags: [feature_flags.last])
end
context 'there are errors' do
let(:failures) do
{
a: [{ message: 'X' }, { message: 'Y' }],
b: [{ message: 'Z' }]
}
end
it 'reports the errors' do
response = subject.send(:store_ff_info, project: project, feature_flags: feature_flags)
expect(response['errorMessages']).to eq(['a: X', 'a: Y', 'b: Z'])
end
end
it 'does not call the API if the feature flag is not enabled' do
stub_feature_flags(jira_sync_feature_flags: false)
......@@ -217,7 +264,7 @@ RSpec.describe Atlassian::JiraConnect::Client do
expect(subject).to receive(:post).with('/rest/featureflags/0.1/bulk', {
flags: Array, properties: Hash
})
}).and_call_original
subject.send(:store_ff_info, project: project, feature_flags: feature_flags)
end
......@@ -234,10 +281,20 @@ RSpec.describe Atlassian::JiraConnect::Client do
->(text) { matcher.matches?(text) }
end
let(:failures) { [] }
let(:response_body) do
{
acceptedBuilds: [],
rejectedBuilds: failures,
unknownIssueKeys: []
}.to_json
end
before do
path = '/rest/builds/0.1/bulk'
stub_full_request('https://gitlab-test.atlassian.net' + path, method: :post)
.with(body: body, headers: expected_headers(path))
.to_return(body: response_body, headers: { 'Content-Type': 'application/json' })
end
it "calls the API with auth headers" do
......@@ -245,7 +302,9 @@ RSpec.describe Atlassian::JiraConnect::Client do
end
it 'only sends information about relevant MRs' do
expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: have_attributes(size: 6) })
expect(subject).to receive(:post)
.with('/rest/builds/0.1/bulk', { builds: have_attributes(size: 6) })
.and_call_original
subject.send(:store_build_info, project: project, pipelines: pipelines)
end
......@@ -267,11 +326,25 @@ RSpec.describe Atlassian::JiraConnect::Client do
it 'does call the API if the feature flag enabled for the project' do
stub_feature_flags(jira_sync_builds: project)
expect(subject).to receive(:post).with('/rest/builds/0.1/bulk', { builds: Array })
expect(subject).to receive(:post)
.with('/rest/builds/0.1/bulk', { builds: Array })
.and_call_original
subject.send(:store_build_info, project: project, pipelines: pipelines)
end
context 'there are errors' do
let(:failures) do
[{ errors: [{ message: 'X' }, { message: 'Y' }] }, { errors: [{ message: 'Z' }] }]
end
it 'reports the errors' do
response = subject.send(:store_build_info, project: project, pipelines: pipelines)
expect(response['errorMessages']).to eq(%w(X Y Z))
end
end
it 'avoids N+1 database queries' do
pending 'https://gitlab.com/gitlab-org/gitlab/-/issues/292818'
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment