From 15c76231c6ed88030ebf08b686a3c8191d7f36c9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda <shinya@gitlab.com> Date: Thu, 25 Aug 2022 12:29:45 +0000 Subject: [PATCH] Extend deployments graphql query for index page This commit extends the deployments graphql query for index page. Changelog: added --- app/finders/deployments_finder.rb | 1 + app/graphql/types/ci/job_type.rb | 6 + app/graphql/types/deployment_type.rb | 15 ++ app/models/deployment.rb | 4 + doc/api/graphql/reference/index.md | 7 + spec/graphql/types/ci/job_type_spec.rb | 15 ++ .../types/deployment_details_type_spec.rb | 2 +- spec/graphql/types/deployment_type_spec.rb | 2 +- spec/models/deployment_spec.rb | 16 ++ .../environments/deployments_query_spec.rb | 141 ++++++++++++++++++ 10 files changed, 207 insertions(+), 2 deletions(-) diff --git a/app/finders/deployments_finder.rb b/app/finders/deployments_finder.rb index e5e08d2971fa3..5b2139cb941bf 100644 --- a/app/finders/deployments_finder.rb +++ b/app/finders/deployments_finder.rb @@ -211,6 +211,7 @@ def preload_associations(scope) environment: [], deployable: { job_artifacts: [], + user: [], pipeline: { project: { route: [], diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 4ea9a016e74e4..ab6103d946943 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -92,6 +92,8 @@ class JobType < BaseObject description: 'Indicates the job is stuck.' field :triggered, GraphQL::Types::Boolean, null: true, description: 'Whether the job was triggered.' + field :web_path, GraphQL::Types::String, null: true, + description: 'Web path of the job.' def kind return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class) @@ -181,6 +183,10 @@ def ref_path ::Gitlab::Routing.url_helpers.project_commits_path(object.project, ref_name) end + def web_path + ::Gitlab::Routing.url_helpers.project_job_path(object.project, object) + end + def coverage object&.coverage end diff --git a/app/graphql/types/deployment_type.rb b/app/graphql/types/deployment_type.rb index 1b61001fc10a4..70a3a4cb57495 100644 --- a/app/graphql/types/deployment_type.rb +++ b/app/graphql/types/deployment_type.rb @@ -50,5 +50,20 @@ class DeploymentType < BaseObject field :status, Types::DeploymentStatusEnum, description: 'Status of the deployment.' + + field :commit, + Types::CommitType, + description: 'Commit details of the deployment.', + calls_gitaly: true + + field :job, + Types::Ci::JobType, + description: 'Pipeline job of the deployment.', + method: :build + + field :triggerer, + Types::UserType, + description: 'User who executed the deployment.', + method: :deployed_by end end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 325754f001abc..a463448c2065d 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -222,6 +222,10 @@ def self.builds(limit = 1000) Ci::Build.where(id: deployable_ids) end + def build + deployable if deployable.is_a?(::Ci::Build) + end + class << self ## # FastDestroyAll concerns diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 68afb17f4859c..5336c1ce4cf8a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10199,6 +10199,7 @@ CI/CD variables for a GitLab instance. | <a id="cijobtags"></a>`tags` | [`[String!]`](#string) | Tags for the current job. | | <a id="cijobtriggered"></a>`triggered` | [`Boolean`](#boolean) | Whether the job was triggered. | | <a id="cijobuserpermissions"></a>`userPermissions` | [`JobPermissions!`](#jobpermissions) | Permissions for the current user on the resource. | +| <a id="cijobwebpath"></a>`webPath` | [`String`](#string) | Web path of the job. | ### `CiJobArtifact` @@ -11019,14 +11020,17 @@ The deployment of an environment. | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="deploymentcommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. | | <a id="deploymentcreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. | | <a id="deploymentfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. | | <a id="deploymentid"></a>`id` | [`ID`](#id) | Global ID of the deployment. | | <a id="deploymentiid"></a>`iid` | [`ID`](#id) | Project-level internal ID of the deployment. | +| <a id="deploymentjob"></a>`job` | [`CiJob`](#cijob) | Pipeline job of the deployment. | | <a id="deploymentref"></a>`ref` | [`String`](#string) | Git-Ref that the deployment ran on. | | <a id="deploymentsha"></a>`sha` | [`String`](#string) | Git-SHA that the deployment ran on. | | <a id="deploymentstatus"></a>`status` | [`DeploymentStatus`](#deploymentstatus) | Status of the deployment. | | <a id="deploymenttag"></a>`tag` | [`Boolean`](#boolean) | True or false if the deployment ran on a Git-tag. | +| <a id="deploymenttriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. | | <a id="deploymentupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. | ### `DeploymentDetails` @@ -11037,14 +11041,17 @@ The details of the deployment. | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="deploymentdetailscommit"></a>`commit` | [`Commit`](#commit) | Commit details of the deployment. | | <a id="deploymentdetailscreatedat"></a>`createdAt` | [`Time`](#time) | When the deployment record was created. | | <a id="deploymentdetailsfinishedat"></a>`finishedAt` | [`Time`](#time) | When the deployment finished. | | <a id="deploymentdetailsid"></a>`id` | [`ID`](#id) | Global ID of the deployment. | | <a id="deploymentdetailsiid"></a>`iid` | [`ID`](#id) | Project-level internal ID of the deployment. | +| <a id="deploymentdetailsjob"></a>`job` | [`CiJob`](#cijob) | Pipeline job of the deployment. | | <a id="deploymentdetailsref"></a>`ref` | [`String`](#string) | Git-Ref that the deployment ran on. | | <a id="deploymentdetailssha"></a>`sha` | [`String`](#string) | Git-SHA that the deployment ran on. | | <a id="deploymentdetailsstatus"></a>`status` | [`DeploymentStatus`](#deploymentstatus) | Status of the deployment. | | <a id="deploymentdetailstag"></a>`tag` | [`Boolean`](#boolean) | True or false if the deployment ran on a Git-tag. | +| <a id="deploymentdetailstriggerer"></a>`triggerer` | [`UserCore`](#usercore) | User who executed the deployment. | | <a id="deploymentdetailsupdatedat"></a>`updatedAt` | [`Time`](#time) | When the deployment record was updated. | ### `Design` diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index bc9e64282bcf9..b3dee082d1fcc 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Types::Ci::JobType do + include GraphqlHelpers + specify { expect(described_class.graphql_name).to eq('CiJob') } specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Job) } @@ -45,8 +47,21 @@ tags triggered userPermissions + webPath ] expect(described_class).to have_graphql_fields(*expected_fields) end + + describe '#web_path' do + subject { resolve_field(:web_path, build, current_user: user, object_type: described_class) } + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:build) { create(:ci_build, project: project, user: user) } + + it 'returns the web path of the job' do + is_expected.to eq("/#{project.full_path}/-/jobs/#{build.id}") + end + end end diff --git a/spec/graphql/types/deployment_details_type_spec.rb b/spec/graphql/types/deployment_details_type_spec.rb index 58756798ffb63..4cbc4165f702c 100644 --- a/spec/graphql/types/deployment_details_type_spec.rb +++ b/spec/graphql/types/deployment_details_type_spec.rb @@ -7,7 +7,7 @@ it 'has the expected fields' do expected_fields = %w[ - id iid ref tag sha created_at updated_at finished_at status + id iid ref tag sha created_at updated_at finished_at status commit job triggerer ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/deployment_type_spec.rb b/spec/graphql/types/deployment_type_spec.rb index 21e445c24b2b1..bf4be0523c64e 100644 --- a/spec/graphql/types/deployment_type_spec.rb +++ b/spec/graphql/types/deployment_type_spec.rb @@ -7,7 +7,7 @@ it 'has the expected fields' do expected_fields = %w[ - id iid ref tag sha created_at updated_at finished_at status + id iid ref tag sha created_at updated_at finished_at status commit job triggerer ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index c5ce18739aa65..2fdca7114eba6 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -897,6 +897,22 @@ def subject_method(environment) end end + describe '#build' do + let!(:deployment) { create(:deployment) } + + subject { deployment.build } + + it 'retrieves build for the deployment' do + is_expected.to eq(deployment.deployable) + end + + it 'returns nil when the associated build is not found' do + deployment.update!(deployable_id: nil, deployable_type: nil) + + is_expected.to be_nil + end + end + describe '#previous_deployment' do using RSpec::Parameterized::TableSyntax diff --git a/spec/requests/api/graphql/environments/deployments_query_spec.rb b/spec/requests/api/graphql/environments/deployments_query_spec.rb index fbfd9c5d0ac64..95cc83e9463c9 100644 --- a/spec/requests/api/graphql/environments/deployments_query_spec.rb +++ b/spec/requests/api/graphql/environments/deployments_query_spec.rb @@ -295,6 +295,147 @@ end end + shared_examples_for 'avoids N+1 database queries' do + it 'does not increase the query count' do + create_deployments + + baseline = ActiveRecord::QueryRecorder.new do + run_with_clean_state(query, context: { current_user: user }) + end + + create_deployments + + multi = ActiveRecord::QueryRecorder.new do + run_with_clean_state(query, context: { current_user: user }) + end + + expect(multi).not_to exceed_query_limit(baseline) + end + + def create_deployments + create_list(:deployment, 3, environment: environment, project: project).each do |deployment| + deployment.user = create(:user).tap { |u| project.add_developer(u) } + deployment.deployable = + create(:ci_build, project: project, environment: environment.name, deployment: deployment, + user: deployment.user) + + deployment.save! + end + end + end + + context 'when requesting commits of deployments' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environment(name: "#{environment.name}") { + deployments { + nodes { + iid + commit { + author { + avatarUrl + name + webPath + } + fullTitle + webPath + sha + } + } + } + } + } + } + ) + end + + it_behaves_like 'avoids N+1 database queries' + + it 'returns commits of deployments' do + deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes') + + deployments.each do |deployment| + deployment_in_record = project.deployments.find_by_iid(deployment['iid']) + + expect(deployment_in_record.sha).to eq(deployment['commit']['sha']) + end + end + end + + context 'when requesting triggerers of deployments' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environment(name: "#{environment.name}") { + deployments { + nodes { + iid + triggerer { + id + avatarUrl + name + webPath + } + } + } + } + } + } + ) + end + + it_behaves_like 'avoids N+1 database queries' + + it 'returns triggerers of deployments' do + deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes') + + deployments.each do |deployment| + deployment_in_record = project.deployments.find_by_iid(deployment['iid']) + + expect(deployment_in_record.deployed_by.name).to eq(deployment['triggerer']['name']) + end + end + end + + context 'when requesting jobs of deployments' do + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + environment(name: "#{environment.name}") { + deployments { + nodes { + iid + job { + id + status + name + webPath + } + } + } + } + } + } + ) + end + + it_behaves_like 'avoids N+1 database queries' + + it 'returns jobs of deployments' do + deployments = subject.dig('data', 'project', 'environment', 'deployments', 'nodes') + + deployments.each do |deployment| + deployment_in_record = project.deployments.find_by_iid(deployment['iid']) + + expect(deployment_in_record.build.to_global_id.to_s).to eq(deployment['job']['id']) + end + end + end + describe 'sorting and pagination' do let(:data_path) { [:project, :environment, :deployments] } let(:current_user) { user } -- GitLab