diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index ec9b09a3419daacfeb238587c446fffc73eaa0f2..0048999a8d2fd9c2cfb8a47ecd022f386630e1c3 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -129,6 +129,14 @@ class Runners < ::API::Base authenticate_list_runners_jobs!(runner) jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute + jobs = jobs.preload( # rubocop: disable CodeReuse/ActiveRecord + [ + :user, + { pipeline: { project: [:route, { namespace: :route }] } }, + { project: [:route, { namespace: :route }] } + ] + ) + jobs.each(&:commit) # batch loads all commits present paginate(jobs), with: Entities::Ci::JobBasicWithProject end diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index 31b85a0b1d6d0b0519e46628b66bd9c2f28ae7de..a24bf22cd459d42b9c9c53223ec0356dae2296fa 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -889,6 +889,38 @@ def update_runner(id, user, args) end end + it 'avoids N+1 DB queries' do + get api("/runners/#{shared_runner.id}/jobs", admin) + + control = ActiveRecord::QueryRecorder.new do + get api("/runners/#{shared_runner.id}/jobs", admin) + end + + create(:ci_build, :failed, runner: shared_runner, project: create(:project)) + + expect do + get api("/runners/#{shared_runner.id}/jobs", admin) + end.not_to exceed_query_limit(control.count) + end + + it 'batches loading of commits' do + shared_runner = create(:ci_runner, :instance, description: 'Shared runner') + + project_with_repo = create(:project, :repository) + + pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b') + create(:ci_build, :running, runner: shared_runner, project: project_with_repo, pipeline: pipeline) + + pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478') + create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline) + + expect_next_instance_of(Repository) do |repo| + expect(repo).to receive(:commits_by).once.and_call_original + end + + get api("/runners/#{shared_runner.id}/jobs", admin) + end + context "when runner doesn't exist" do it 'returns 404' do get api('/runners/0/jobs', admin)