Commit 0f3cd731 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'mo-add-coquality-merge-request' into 'master'

Add coquality comparison for merge request

See merge request gitlab-org/gitlab!48830
parents 7ff19f72 41512f97
...@@ -21,7 +21,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -21,7 +21,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
:exposed_artifacts, :exposed_artifacts,
:coverage_reports, :coverage_reports,
:terraform_reports, :terraform_reports,
:accessibility_reports :accessibility_reports,
:codequality_reports
] ]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
...@@ -194,6 +195,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -194,6 +195,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end end
end end
def codequality_reports
reports_response(@merge_request.compare_codequality_reports)
end
def terraform_reports def terraform_reports
reports_response(@merge_request.find_terraform_reports) reports_response(@merge_request.find_terraform_reports)
end end
......
...@@ -1461,6 +1461,20 @@ class MergeRequest < ApplicationRecord ...@@ -1461,6 +1461,20 @@ class MergeRequest < ApplicationRecord
compare_reports(Ci::GenerateCoverageReportsService) compare_reports(Ci::GenerateCoverageReportsService)
end end
def has_codequality_reports?
return false unless Feature.enabled?(:codequality_mr_diff, project)
actual_head_pipeline&.has_reports?(Ci::JobArtifact.codequality_reports)
end
def compare_codequality_reports
unless has_codequality_reports?
return { status: :error, status_reason: _('This merge request does not have codequality reports') }
end
compare_reports(Ci::CompareCodequalityReportsService)
end
def find_terraform_reports def find_terraform_reports
unless has_terraform_reports? unless has_terraform_reports?
return { status: :error, status_reason: 'This merge request does not have terraform reports' } return { status: :error, status_reason: 'This merge request does not have terraform reports' }
......
# frozen_string_literal: true
module Ci
class CompareCodequalityReportsService < CompareReportsBaseService
def comparer_class
Gitlab::Ci::Reports::CodequalityReportsComparer
end
def serializer_class
CodequalityReportsComparerSerializer
end
def get_report(pipeline)
pipeline&.codequality_reports
end
end
end
---
name: codequality_mr_diff
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47938
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284140
milestone: '13.7'
type: development
group: group::testing
default_enabled: false
...@@ -17,6 +17,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], ...@@ -17,6 +17,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show],
get :accessibility_reports get :accessibility_reports
get :coverage_reports get :coverage_reports
get :terraform_reports get :terraform_reports
get :codequality_reports
scope constraints: ->(req) { req.format == :json }, as: :json do scope constraints: ->(req) { req.format == :json }, as: :json do
get :commits get :commits
......
...@@ -28185,6 +28185,9 @@ msgstr "" ...@@ -28185,6 +28185,9 @@ msgstr ""
msgid "This merge request does not have accessibility reports" msgid "This merge request does not have accessibility reports"
msgstr "" msgstr ""
msgid "This merge request does not have codequality reports"
msgstr ""
msgid "This merge request is closed. To apply this suggestion, edit this file directly." msgid "This merge request is closed. To apply this suggestion, edit this file directly."
msgstr "" msgstr ""
......
...@@ -1498,6 +1498,121 @@ RSpec.describe Projects::MergeRequestsController do ...@@ -1498,6 +1498,121 @@ RSpec.describe Projects::MergeRequestsController do
end end
end end
describe 'GET codequality_reports' do
let_it_be(:merge_request) do
create(:merge_request,
:with_diffs,
:with_merge_request_pipeline,
target_project: project,
source_project: project
)
end
let(:pipeline) do
create(:ci_pipeline,
:success,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
before do
allow_any_instance_of(MergeRequest)
.to receive(:compare_codequality_reports)
.and_return(codequality_comparison)
allow_any_instance_of(MergeRequest)
.to receive(:actual_head_pipeline)
.and_return(pipeline)
end
subject do
get :codequality_reports, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid
},
format: :json
end
context 'permissions on a public project with private CI/CD' do
let(:project) { project_public_with_private_builds }
let(:codequality_comparison) { { status: :parsed, data: { summary: 1 } } }
context 'while signed out' do
before do
sign_out(user)
end
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to be_blank
end
end
context 'while signed in as an unrelated user' do
before do
sign_in(create(:user))
end
it 'responds with a 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
expect(response.body).to be_blank
end
end
end
context 'when pipeline has jobs with codequality reports' do
before do
allow_any_instance_of(MergeRequest)
.to receive(:has_codequality_reports?)
.and_return(true)
end
context 'when processing codequality reports is in progress' do
let(:codequality_comparison) { { status: :parsing } }
it 'sends polling interval' do
expect(Gitlab::PollingInterval).to receive(:set_header)
subject
end
it 'returns 204 HTTP status' do
subject
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'when processing codequality reports is completed' do
let(:codequality_comparison) { { status: :parsed, data: { summary: 1 } } }
it 'returns codequality reports' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq({ 'summary' => 1 })
end
end
end
context 'when pipeline has job without a codequality report' do
let(:codequality_comparison) { { status: :error, status_reason: 'no codequality report' } }
it 'returns a 400' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq({ 'status_reason' => 'no codequality report' })
end
end
end
describe 'POST remove_wip' do describe 'POST remove_wip' do
before do before do
merge_request.title = merge_request.wip_title merge_request.title = merge_request.wip_title
......
...@@ -159,6 +159,18 @@ FactoryBot.define do ...@@ -159,6 +159,18 @@ FactoryBot.define do
end end
end end
trait :with_codequality_reports do
after(:build) do |merge_request|
merge_request.head_pipeline = build(
:ci_pipeline,
:success,
:with_codequality_reports,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
end
trait :unique_branches do trait :unique_branches do
source_branch { generate(:branch) } source_branch { generate(:branch) }
target_branch { generate(:branch) } target_branch { generate(:branch) }
......
...@@ -1929,6 +1929,32 @@ RSpec.describe MergeRequest, factory_default: :keep do ...@@ -1929,6 +1929,32 @@ RSpec.describe MergeRequest, factory_default: :keep do
end end
end end
describe '#has_codequality_reports?' do
subject { merge_request.has_codequality_reports? }
let(:project) { create(:project, :repository) }
context 'when head pipeline has a codequality report' do
let(:merge_request) { create(:merge_request, :with_codequality_reports, source_project: project) }
it { is_expected.to be_truthy }
context 'when feature flag is disabled' do
before do
stub_feature_flags(codequality_mr_diff: false)
end
it { is_expected.to be_falsey }
end
end
context 'when head pipeline does not have a codequality report' do
let(:merge_request) { create(:merge_request, source_project: project) }
it { is_expected.to be_falsey }
end
end
describe '#has_terraform_reports?' do describe '#has_terraform_reports?' do
context 'when head pipeline has terraform reports' do context 'when head pipeline has terraform reports' do
it 'returns true' do it 'returns true' do
...@@ -2205,6 +2231,62 @@ RSpec.describe MergeRequest, factory_default: :keep do ...@@ -2205,6 +2231,62 @@ RSpec.describe MergeRequest, factory_default: :keep do
end end
end end
describe '#compare_codequality_reports' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:merge_request, reload: true) { create(:merge_request, :with_codequality_reports, source_project: project) }
let_it_be(:pipeline) { merge_request.head_pipeline }
subject { merge_request.compare_codequality_reports }
context 'when head pipeline has codequality report' do
let(:job) do
create(:ci_build, options: { artifacts: { reports: { codeclimate: ['codequality.json'] } } }, pipeline: pipeline)
end
let(:artifacts_metadata) { create(:ci_job_artifact, :metadata, job: job) }
context 'when reactive cache worker is parsing results asynchronously' do
it 'returns parsing status' do
expect(subject[:status]).to eq(:parsing)
end
end
context 'when reactive cache worker is inline' do
before do
synchronous_reactive_cache(merge_request)
end
it 'returns parsed status' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]).to be_present
end
context 'when an error occurrs' do
before do
merge_request.update!(head_pipeline: nil)
end
it 'returns an error status' do
expect(subject[:status]).to eq(:error)
expect(subject[:status_reason]).to eq("This merge request does not have codequality reports")
end
end
context 'when cached result is not latest' do
before do
allow_next_instance_of(Ci::CompareCodequalityReportsService) do |service|
allow(service).to receive(:latest?).and_return(false)
end
end
it 'raises an InvalidateReactiveCache error' do
expect { subject }.to raise_error(ReactiveCaching::InvalidateReactiveCache)
end
end
end
end
end
describe '#all_commit_shas' do describe '#all_commit_shas' do
context 'when merge request is persisted' do context 'when merge request is persisted' do
let(:all_commit_shas) do let(:all_commit_shas) do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CompareCodequalityReportsService do
let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) }
describe '#execute' do
subject { service.execute(base_pipeline, head_pipeline) }
context 'when head pipeline has a codequality report' do
let(:base_pipeline) { nil }
let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
it 'returns status and data' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
end
end
context 'when base and head pipelines have codequality reports' do
let(:base_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
let(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
it 'returns status and data' do
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]).to match_schema('entities/codequality_reports_comparer')
end
end
end
describe '#latest?' do
subject { service.latest?(base_pipeline, head_pipeline, data) }
let!(:base_pipeline) { nil }
let!(:head_pipeline) { create(:ci_pipeline, :with_codequality_reports, project: project) }
let!(:key) { service.send(:key, base_pipeline, head_pipeline) }
context 'when cache key is latest' do
let(:data) { { key: key } }
it { is_expected.to be_truthy }
end
context 'when cache key is outdated' do
before do
head_pipeline.update_column(:updated_at, 10.minutes.ago)
end
let(:data) { { key: key } }
it { is_expected.to be_falsy }
end
context 'when cache key is empty' do
let(:data) { { key: nil } }
it { is_expected.to be_falsy }
end
end
end
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