Commit d0e635a0 authored by Andreas Brandl's avatar Andreas Brandl

Merge branch 'pedropombeiro/331598/filter-runners-by-text' into 'master'

Implement search argument in RunnersResolver

See merge request gitlab-org/gitlab!63036
parents abdf1d42 fa3da496
......@@ -17,6 +17,10 @@ module Resolvers
required: false,
description: 'Filter by tags associated with the runner (comma-separated or array).'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by full token or partial text in description field.'
argument :sort, ::Types::Ci::RunnerSortEnum,
required: false,
description: 'Sort order of results.'
......
......@@ -168,18 +168,13 @@ module Ci
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL.
#
# This method performs a *partial* match on tokens, thus a query for "a"
# will match any runner where the token contains the letter "a". As a result
# you should *not* use this method for non-admin purposes as otherwise users
# might be able to query a list of all runners.
# This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
#
# query - The search query as a String.
#
# Returns an ActiveRecord::Relation.
def self.search(query)
fuzzy_search(query, [:token, :description])
where(token: query).or(fuzzy_search(query, [:description]))
end
def self.online_contact_time_deadline
......
# frozen_string_literal: true
class AddRunnersDescriptionIndex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
INDEX_NAME = 'index_ci_runners_on_description_trigram'
disable_ddl_transaction!
def up
add_concurrent_index :ci_runners, :description, name: INDEX_NAME, using: :gin, opclass: { description: :gin_trgm_ops }
end
def down
remove_concurrent_index_by_name :ci_runners, INDEX_NAME
end
end
96c70de2567fc3e816c720ed6e4cef2446c0f0ee288d0959cd1298523913077f
\ No newline at end of file
......@@ -22921,6 +22921,8 @@ CREATE INDEX index_ci_runner_projects_on_runner_id ON ci_runner_projects USING b
CREATE INDEX index_ci_runners_on_contacted_at ON ci_runners USING btree (contacted_at);
CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);
CREATE INDEX index_ci_runners_on_locked ON ci_runners USING btree (locked);
CREATE INDEX index_ci_runners_on_runner_type ON ci_runners USING btree (runner_type);
......@@ -336,6 +336,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryrunnerssearch"></a>`search` | [`String`](#string) | Filter by full token or partial text in description field. |
| <a id="queryrunnerssort"></a>`sort` | [`CiRunnerSort`](#cirunnersort) | Sort order of results. |
| <a id="queryrunnersstatus"></a>`status` | [`CiRunnerStatus`](#cirunnerstatus) | Filter runners by status. |
| <a id="queryrunnerstaglist"></a>`tagList` | [`[String!]`](#string) | Filter by tags associated with the runner (comma-separated or array). |
......
......@@ -41,6 +41,7 @@ GET /runners?scope=active
GET /runners?type=project_type
GET /runners?status=active
GET /runners?tag_list=tag1,tag2
GET /runners?search=gitlab
```
| Attribute | Type | Required | Description |
......@@ -49,6 +50,7 @@ GET /runners?tag_list=tag1,tag2
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
| `search` | string | no | The full token or partial description text to match |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
......
......@@ -10,15 +10,15 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:inactive_project_runner) do
create(:ci_runner, :project, projects: [project], active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner))
end
let_it_be(:offline_project_runner) do
create(:ci_runner, :project, projects: [project], contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner))
end
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) }
describe '#resolve' do
subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a }
......@@ -27,6 +27,14 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
{}
end
context 'when the user cannot see runners' do
let(:user) { create(:user) }
it 'returns no runners' do
is_expected.to be_empty
end
end
context 'without sort' do
it 'returns all the runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner)
......@@ -132,5 +140,35 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
end
end
end
context 'when text is filtered' do
let(:args) do
{ search: search_term }
end
context 'to "project"' do
let(:search_term) { 'project' }
it 'returns both project runners' do
is_expected.to contain_exactly(inactive_project_runner, offline_project_runner)
end
end
context 'to "group"' do
let(:search_term) { 'group' }
it 'returns group runner' do
is_expected.to contain_exactly(group_runner)
end
end
context 'to "defghi"' do
let(:search_term) { 'defghi' }
it 'returns runners containing term in token' do
is_expected.to contain_exactly(offline_project_runner)
end
end
end
end
end
......@@ -873,12 +873,12 @@ RSpec.describe Ci::Runner do
expect(described_class.search(runner.token)).to eq([runner])
end
it 'returns runners with a partially matching token' do
expect(described_class.search(runner.token[0..2])).to eq([runner])
it 'does not return runners with a partially matching token' do
expect(described_class.search(runner.token[0..2])).to be_empty
end
it 'returns runners with a matching token regardless of the casing' do
expect(described_class.search(runner.token.upcase)).to eq([runner])
it 'does not return runners with a matching token with different casing' do
expect(described_class.search(runner.token.upcase)).to be_empty
end
it 'returns runners with a matching description' do
......
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