Commit f7080968 authored by Miguel Rincon's avatar Miguel Rincon Committed by Alex Kalderimis

Return runner webUrl via GraphQL API

This change adds the web url of a runner to the
GraphQL API. The URL will vary according to usage of the API
in a Group or Project.

Changelog: added
parent ee41d6a8
......@@ -3,6 +3,7 @@
module Types
module Ci
class RunnerType < BaseObject
edge_type_class(RunnerWebUrlEdge)
graphql_name 'CiRunner'
authorize :read_runner
present_using ::Ci::RunnerPresenter
......@@ -48,12 +49,18 @@ module Types
description: 'Number of projects that the runner is associated with.'
field :job_count, GraphQL::Types::Int, null: true,
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
field :admin_url, GraphQL::Types::String, null: true,
description: 'Admin URL of the runner. Only available for adminstrators.'
def job_count
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
runner.builds.limit(JOB_COUNT_LIMIT + 1).count
end
def admin_url
Gitlab::Routing.url_helpers.admin_runner_url(runner) if can_admin_runners?
end
# rubocop: disable CodeReuse/ActiveRecord
def project_count
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args|
......@@ -70,6 +77,12 @@ module Types
end
end
# rubocop: enable CodeReuse/ActiveRecord
private
def can_admin_runners?
context[:current_user]&.can_admin_all_resources?
end
end
end
end
......
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class RunnerWebUrlEdge < GraphQL::Types::Relay::BaseEdge
include FindClosest
field :web_url, GraphQL::Types::String, null: true,
description: 'Web URL of the runner. The value depends on where you put this field in the query. You can use it for projects or groups.',
extras: [:parent]
def initialize(node, connection)
super
@runner = node.node
end
def web_url(parent:)
owner = closest_parent([::Types::ProjectType, ::Types::GroupType], parent)
case owner
when ::Group
Gitlab::Routing.url_helpers.group_runner_url(owner, @runner)
when ::Project
Gitlab::Routing.url_helpers.project_runner_url(owner, @runner)
end
end
end
end
end
# frozen_string_literal: true
module FindClosest
# Find the closest node of a given type above this node, and return the domain object
def closest_parent(type, parent)
parent = parent.try(:parent) while parent && parent.object.class != type
return unless parent
# Find the closest node which has any of the given types above this node, and return the domain object
def closest_parent(types, parent)
while parent
parent.object.object
if types.any? {|type| parent.object.instance_of? type}
return parent.object.object
else
parent = parent.try(:parent)
end
end
end
end
......@@ -14,7 +14,7 @@ module Types
end
def merge_request_interaction(parent:)
merge_request = closest_parent(::Types::MergeRequestType, parent)
merge_request = closest_parent([::Types::MergeRequestType], parent)
return unless merge_request
Users::MergeRequestInteraction.new(user: object, merge_request: merge_request)
......
......@@ -5180,6 +5180,7 @@ The edge type for [`CiRunner`](#cirunner).
| ---- | ---- | ----------- |
| <a id="cirunneredgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cirunneredgenode"></a>`node` | [`CiRunner`](#cirunner) | The item at the end of the edge. |
| <a id="cirunneredgeweburl"></a>`webUrl` | [`String`](#string) | Web URL of the runner. The value depends on where you put this field in the query. You can use it for projects or groups. |
#### `CiStageConnection`
......@@ -8399,6 +8400,7 @@ Represents the total number of issues and their weights for a particular day.
| ---- | ---- | ----------- |
| <a id="cirunneraccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel!`](#cirunneraccesslevel) | Access level of the runner. |
| <a id="cirunneractive"></a>`active` | [`Boolean!`](#boolean) | Indicates the runner is allowed to receive jobs. |
| <a id="cirunneradminurl"></a>`adminUrl` | [`String`](#string) | Admin URL of the runner. Only available for adminstrators. |
| <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Last contact from the runner. |
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
......
......@@ -11,7 +11,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
expected_fields = %w[
id description contacted_at maximum_timeout access_level active status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count user_permissions
project_count job_count admin_url user_permissions
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -6,6 +6,7 @@ RSpec.describe 'Query.runner(id)' do
include GraphqlHelpers
let_it_be(:user) { create(:user, :admin) }
let_it_be(:group) { create(:group) }
let_it_be(:active_instance_runner) do
create(:ci_runner, :instance, description: 'Runner 1', contacted_at: 2.hours.ago,
......@@ -18,12 +19,20 @@ RSpec.describe 'Query.runner(id)' do
version: 'adfe157', revision: 'b', ip_address: '10.10.10.10', access_level: 1, run_untagged: true)
end
let_it_be(:active_group_runner) do
create(:ci_runner, :group, groups: [group], description: 'Group runner 1', contacted_at: 2.hours.ago,
active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true)
end
def get_runner(id)
case id
when :active_instance_runner
active_instance_runner
when :inactive_instance_runner
inactive_instance_runner
when :active_group_runner
active_group_runner
end
end
......@@ -62,6 +71,7 @@ RSpec.describe 'Query.runner(id)' do
'runnerType' => runner.instance_type? ? 'INSTANCE_TYPE' : 'PROJECT_TYPE',
'jobCount' => 0,
'projectCount' => nil,
'adminUrl' => "http://localhost/admin/runners/#{runner.id}",
'userPermissions' => {
'readRunner' => true,
'updateRunner' => true,
......@@ -72,6 +82,32 @@ RSpec.describe 'Query.runner(id)' do
end
end
shared_examples 'retrieval with no admin url' do |runner_id|
let(:query) do
wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
end
let(:query_path) do
[
[:runner, { id: get_runner(runner_id).to_global_id.to_s }]
]
end
it 'retrieves expected fields' do
post_graphql(query, current_user: user)
runner_data = graphql_data_at(:runner)
expect(runner_data).not_to be_nil
runner = get_runner(runner_id)
expect(runner_data).to match a_hash_including(
'id' => "gid://gitlab/Ci::Runner/#{runner.id}",
'adminUrl' => nil
)
expect(runner_data['tagList']).to match_array runner.tag_list
end
end
shared_examples 'retrieval by unauthorized user' do |runner_id|
let(:query) do
wrap_fields(query_graphql_path(query_path, all_graphql_fields_for('CiRunner')))
......@@ -152,6 +188,39 @@ RSpec.describe 'Query.runner(id)' do
it_behaves_like 'runner details fetch', :inactive_instance_runner
end
describe 'for runner inside group request' do
let(:query) do
%(
query {
group(fullPath: "#{group.full_path}") {
runners {
edges {
webUrl
node {
id
}
}
}
}
}
)
end
it 'retrieves webUrl field with expected value' do
post_graphql(query, current_user: user)
runner_data = graphql_data_at(:group, :runners, :edges)
expect(runner_data).to match_array [
a_hash_including(
'webUrl' => "http://localhost/groups/#{group.full_path}/-/runners/#{active_group_runner.id}",
'node' => {
'id' => "gid://gitlab/Ci::Runner/#{active_group_runner.id}"
}
)
]
end
end
describe 'for multiple runners' do
let_it_be(:project1) { create(:project, :test_repo) }
let_it_be(:project2) { create(:project, :test_repo) }
......@@ -210,6 +279,16 @@ RSpec.describe 'Query.runner(id)' do
it_behaves_like 'retrieval by unauthorized user', :active_instance_runner
end
describe 'by non-admin user' do
let(:user) { create(:user) }
before do
group.add_user(user, Gitlab::Access::OWNER)
end
it_behaves_like 'retrieval with no admin url', :active_group_runner
end
describe 'by unauthenticated user' do
let(:user) { nil }
......
......@@ -85,6 +85,7 @@
- "./spec/requests/api/ci/runner/runners_post_spec.rb"
- "./spec/requests/api/ci/runners_spec.rb"
- "./spec/requests/api/commit_statuses_spec.rb"
- "./spec/requests/api/graphql/ci/runner_spec.rb"
- "./spec/requests/api/graphql/group_query_spec.rb"
- "./spec/requests/api/graphql/merge_request/merge_request_spec.rb"
- "./spec/requests/api/graphql/mutations/merge_requests/create_spec.rb"
......
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