Commit e9c2fc48 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'filter-commit-sha' into 'master'

Chain of custody report filter by merge commit sha

See merge request gitlab-org/gitlab!46581
parents 48e33a5e 4a293726
...@@ -7,15 +7,21 @@ class Groups::Security::MergeCommitReportsController < Groups::ApplicationContro ...@@ -7,15 +7,21 @@ class Groups::Security::MergeCommitReportsController < Groups::ApplicationContro
feature_category :compliance_management feature_category :compliance_management
def index def index
csv_data = MergeCommits::ExportCsvService.new(current_user, group).csv_data response = MergeCommits::ExportCsvService.new(current_user, group, filter_params).csv_data
respond_to do |format| respond_to do |format|
format.csv do format.csv do
send_data( if response&.success?
csv_data, send_data(
type: 'text/csv; charset=utf-8; header=present', response.payload,
filename: merge_commits_csv_filename type: 'text/csv; charset=utf-8; header=present',
) filename: merge_commits_csv_filename
)
else
flash[:alert] = _('An error occurred while trying to generate the report. Please try again later.')
redirect_to group_security_compliance_dashboard_path(group)
end
end end
end end
end end
...@@ -25,4 +31,8 @@ class Groups::Security::MergeCommitReportsController < Groups::ApplicationContro ...@@ -25,4 +31,8 @@ class Groups::Security::MergeCommitReportsController < Groups::ApplicationContro
def merge_commits_csv_filename def merge_commits_csv_filename
"#{group.id}-merge-commits-#{Time.current.to_i}.csv" "#{group.id}-merge-commits-#{Time.current.to_i}.csv"
end end
def filter_params
params.permit(:commit_sha)
end
end end
...@@ -8,7 +8,10 @@ module EE ...@@ -8,7 +8,10 @@ module EE
override :filter_items override :filter_items
def filter_items(items) def filter_items(items)
items = super(items) items = super(items)
by_approvers(items) items = by_approvers(items)
items = by_merge_commit_sha(items)
items
end end
# Filter by merge requests approval list that contains specified user directly or as part of group membership # Filter by merge requests approval list that contains specified user directly or as part of group membership
...@@ -18,6 +21,12 @@ module EE ...@@ -18,6 +21,12 @@ module EE
.execute(items) .execute(items)
end end
def by_merge_commit_sha(items)
return items unless params[:merge_commit_sha].present?
items.by_merge_commit_sha(params[:merge_commit_sha])
end
class_methods do class_methods do
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
......
...@@ -2,39 +2,44 @@ ...@@ -2,39 +2,44 @@
module MergeCommits module MergeCommits
class ExportCsvService class ExportCsvService
TARGET_FILESIZE = 15_000_000 # file size restricted to 15MB include Gitlab::Utils::StrongMemoize
TARGET_FILESIZE = 15.megabytes
def initialize(current_user, group) def initialize(current_user, group, filter_params = {})
@current_user = current_user @current_user = current_user
@group = group @group = group
@filter_params = filter_params
end end
def csv_data def csv_data
csv_builder.render(TARGET_FILESIZE) ServiceResponse.success(payload: csv_builder.render(TARGET_FILESIZE))
end end
private private
attr_reader :current_user, :group attr_reader :current_user, :group, :filter_params
def csv_builder def csv_builder
@csv_builder ||= CsvBuilder.new(data, header_to_value_hash) @csv_builder ||= CsvBuilder.new(data, header_to_value_hash)
end end
def data def data
MergeRequestsFinder strong_memoize(:merge_commits_data) do
.new(current_user, finder_options) MergeRequestsFinder
.execute .new(current_user, finder_options)
.preload_author .execute
.preload_approved_by_users .preload_author
.preload_target_project .preload_approved_by_users
.preload_metrics([:merged_by]) .preload_target_project
.preload_metrics([:merged_by])
end
end end
def finder_options def finder_options
{ {
group_id: group.id, group_id: group.id,
state: 'merged' state: 'merged',
merge_commit_sha: filter_params[:commit_sha]
} }
end end
......
---
title: Chain of custody report filter by merge commit sha
merge_request: 46581
author:
type: added
...@@ -26,7 +26,9 @@ RSpec.describe Groups::Security::MergeCommitReportsController do ...@@ -26,7 +26,9 @@ RSpec.describe Groups::Security::MergeCommitReportsController do
CSV CSV
end end
let(:export_csv_service) { instance_spy(MergeCommits::ExportCsvService, csv_data: csv_data) } let(:export_csv_service) do
instance_spy(MergeCommits::ExportCsvService, csv_data: ServiceResponse.success(payload: csv_data))
end
before_all do before_all do
group.add_owner(user) group.add_owner(user)
...@@ -72,6 +74,18 @@ RSpec.describe Groups::Security::MergeCommitReportsController do ...@@ -72,6 +74,18 @@ RSpec.describe Groups::Security::MergeCommitReportsController do
]) ])
end end
end end
context 'when invalid' do
let(:export_csv_service) do
instance_spy(MergeCommits::ExportCsvService, csv_data: nil)
end
it do
subject
expect(flash[:alert]).to eq 'An error occurred while trying to generate the report. Please try again later.'
end
end
end end
context 'when user does not have access to dashboard' do context 'when user does not have access to dashboard' do
......
...@@ -13,5 +13,22 @@ RSpec.describe MergeRequestsFinder do ...@@ -13,5 +13,22 @@ RSpec.describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1) expect(merge_requests).to contain_exactly(merge_request1)
end end
context 'merge commit sha' do
let_it_be(:merged_merge_request) do
create(:merge_request, :simple, author: user,
source_project: project4, target_project: project4,
state: :merged, merge_commit_sha: 'rurebf')
end
it 'filters by merge commit sha' do
merge_requests = described_class.new(
user,
merge_commit_sha: merged_merge_request.merge_commit_sha
).execute
expect(merge_requests).to contain_exactly(merged_merge_request)
end
end
end end
end end
...@@ -18,6 +18,8 @@ RSpec.describe MergeCommits::ExportCsvService do ...@@ -18,6 +18,8 @@ RSpec.describe MergeCommits::ExportCsvService do
project.add_maintainer(user) project.add_maintainer(user)
end end
it { expect(subject.csv_data).to be_success }
it 'includes the appropriate headers' do it 'includes the appropriate headers' do
expect(csv.headers).to eq(['Merge Commit', 'Author', 'Merge Request', 'Merged By', 'Pipeline', 'Group', 'Project', 'Approver(s)']) expect(csv.headers).to eq(['Merge Commit', 'Author', 'Merge Request', 'Merged By', 'Pipeline', 'Group', 'Project', 'Approver(s)'])
end end
...@@ -57,14 +59,34 @@ RSpec.describe MergeCommits::ExportCsvService do ...@@ -57,14 +59,34 @@ RSpec.describe MergeCommits::ExportCsvService do
end end
context 'with multiple merge requests' do context 'with multiple merge requests' do
let_it_be(:merge_request_2) { create(:merge_request_with_diffs, source_project: project, target_project: project, state: :merged) } let_it_be(:merge_request_2) { create(:merge_request_with_diffs, source_project: project, target_project: project, state: :merged, merge_commit_sha: 'rurebf') }
it { expect(csv.count).to eq 2 }
context 'by commit_sha filter' do
context 'when valid' do
subject { described_class.new(user, group, { commit_sha: merge_request_2.merge_commit_sha }) }
it { expect(subject.csv_data).to be_success }
it do it { expect(csv.count).to eq 1 }
expect(csv.count).to eq 2
it do
expect(csv[0]['Merge Commit']).to eq merge_request_2.merge_commit_sha
end
end
context 'when merge commit does not exist' do
subject { described_class.new(user, group, { commit_sha: 'inexistent' }) }
it { expect(csv.count).to eq 0 }
end
end end
end end
def csv def csv
CSV.parse(subject.csv_data, headers: true) data = subject.csv_data.payload
CSV.parse(data, headers: true)
end end
end end
...@@ -3198,6 +3198,9 @@ msgstr "" ...@@ -3198,6 +3198,9 @@ msgstr ""
msgid "An error occurred while triggering the job." msgid "An error occurred while triggering the job."
msgstr "" msgstr ""
msgid "An error occurred while trying to generate the report. Please try again later."
msgstr ""
msgid "An error occurred while trying to run a new pipeline for this Merge Request." msgid "An error occurred while trying to run a new pipeline for this Merge Request."
msgstr "" msgstr ""
......
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