diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js index 934375023ba5da9d8911a63e0b42f5c9873f0bf5..691d165c5859d261199145a8e7fc00d4113e34bb 100644 --- a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js @@ -17,6 +17,14 @@ const tokenKeys = [ icon: 'cube', tag: 'type', }, + { + key: 'tag', + type: 'array', + param: 'name[]', + symbol: '~', + icon: 'tag', + tag: '~tag', + }, ]; const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 4d05f46ed175bf846aca4926333223e4b281d930..90293d9619ac53be0ebf6d68682604baa29009a9 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -111,6 +111,15 @@ export default class FilteredSearchDropdownManager { gl: NullDropdown, element: this.container.querySelector('#js-dropdown-admin-runner-type'), }, + tag: { + reference: null, + gl: DropdownNonUser, + extraArguments: { + endpoint: this.getRunnerTagsEndpoint(), + symbol: '~', + }, + element: this.container.querySelector('#js-dropdown-runner-tag'), + }, }; supportedTokens.forEach(type => { @@ -146,6 +155,10 @@ export default class FilteredSearchDropdownManager { return endpoint; } + getRunnerTagsEndpoint() { + return `${this.baseEndpoint}/admin/runners/tag_list.json`; + } + static addWordToInput(tokenName, tokenValue = '', clicked = false, options = {}) { const { uppercaseTokenName = false, capitalizeTokenValue = false } = options; const input = FilteredSearchContainer.container.querySelector('.filtered-search'); diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 0b6ff491c66f46a03c16058aa3c130fe0102224a..78edd325f1d3a96baa68e304ba76acd03af4db0c 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Admin::RunnersController < Admin::ApplicationController - before_action :runner, except: :index + before_action :runner, except: [:index, :tag_list] def index finder = Admin::RunnersFinder.new(params: params) @@ -48,6 +48,10 @@ def pause end end + def tag_list + render json: AutocompleteTagsService.new(Ci::Runner).run + end + private def runner diff --git a/app/finders/admin/runners_finder.rb b/app/finders/admin/runners_finder.rb index 8d936b8121c053f2ea97027211f7a5deb378d5e1..b2799565f57c2bb59818cba338fb3063d847a1a2 100644 --- a/app/finders/admin/runners_finder.rb +++ b/app/finders/admin/runners_finder.rb @@ -11,6 +11,7 @@ def execute search! filter_by_status! filter_by_runner_type! + filter_by_tag_list! sort! paginate! @@ -44,6 +45,14 @@ def filter_by_runner_type! filter_by!(:type_type, Ci::Runner::AVAILABLE_TYPES) end + def filter_by_tag_list! + tag_list = @params[:tag_name].presence + + if tag_list + @runners = @runners.tagged_with(tag_list) + end + end + def sort! @runners = @runners.order_by(sort_key) end diff --git a/app/services/autocomplete_tags_service.rb b/app/services/autocomplete_tags_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..ccd0419e233a0787e357852c42c057ef46bf0f1e --- /dev/null +++ b/app/services/autocomplete_tags_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AutocompleteTagsService + def initialize(taggable_type) + @taggable_type = taggable_type + end + + # rubocop: disable CodeReuse/ActiveRecord + def run + @taggable_type + .all_tags + .pluck(:id, :name).map do |id, name| + { id: id, title: name } + end + end + # rubocop: enable CodeReuse/ActiveRecord +end diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 81380587fd2d1e56415017ae58c240b90402026e..f0a0a1897c712d6b0f286e05ed591233889fbaf4 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -92,6 +92,24 @@ = button_tag class: %w[btn btn-link] do = runner_type.titleize + #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| + %li.filter-dropdown-item{ data: { value: runner_type } } + = button_tag class: %w[btn btn-link] do + = runner_type.titleize + + #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { value: 'none' } } + %button.btn.btn-link + = _('No Tag') + %li.divider.droplab-item-ignore + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + %button.btn.btn-link.js-data-value + {{title}} + = button_tag class: %w[clear-search hidden] do = icon('times') .filter-dropdown-container diff --git a/config/routes/admin.rb b/config/routes/admin.rb index af333bdc74835b08cf733cc21472103c3ee0f724..a01003b603935100238994bb76fd2d19f657b790 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -120,6 +120,10 @@ get :resume get :pause end + + collection do + get :tag_list, format: :json + end end resources :jobs, only: :index do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 588dd766bdcbc3dc86f05b274e990efaf8f2647b..3cd6d9a88ce70107bb131423b6912f306b6ec507 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4943,6 +4943,9 @@ msgstr "" msgid "No %{providerTitle} repositories available to import" msgstr "" +msgid "No Tag" +msgstr "" + msgid "No activities found" msgstr "" diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index ed9c0ea9ac05faee9a45f9ea88e846cf73a8f9d9..97b432a6751e56fff9538e1b6acf6a848d87a4d2 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -141,6 +141,56 @@ end end + describe 'filter by tag', :js do + it 'shows correct runner when tag matches' do + create :ci_runner, description: 'runner-blue', tag_list: ['blue'] + create :ci_runner, description: 'runner-red', tag_list: ['red'] + + visit admin_runners_path + + expect(page).to have_content 'runner-blue' + expect(page).to have_content 'runner-red' + + input_filtered_search_keys('tag:blue') + + expect(page).to have_content 'runner-blue' + expect(page).not_to have_content 'runner-red' + end + + it 'shows no runner when tag does not match' do + create :ci_runner, description: 'runner-blue', tag_list: ['blue'] + create :ci_runner, description: 'runner-red', tag_list: ['blue'] + + visit admin_runners_path + + input_filtered_search_keys('tag:red') + + expect(page).not_to have_content 'runner-blue' + expect(page).not_to have_content 'runner-blue' + expect(page).to have_text 'No runners found' + end + + it 'shows correct runner when tag is selected and search term is entered' do + create :ci_runner, description: 'runner-a-1', tag_list: ['blue'] + create :ci_runner, description: 'runner-a-2', tag_list: ['red'] + create :ci_runner, description: 'runner-b-1', tag_list: ['blue'] + + visit admin_runners_path + + input_filtered_search_keys('tag:blue') + + expect(page).to have_content 'runner-a-1' + expect(page).to have_content 'runner-b-1' + expect(page).not_to have_content 'runner-a-2' + + input_filtered_search_keys('tag:blue runner-a') + + expect(page).to have_content 'runner-a-1' + expect(page).not_to have_content 'runner-b-1' + expect(page).not_to have_content 'runner-a-2' + end + end + it 'sorts by last contact date', :js do create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37') create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37') diff --git a/spec/finders/admin/runners_finder_spec.rb b/spec/finders/admin/runners_finder_spec.rb index 0b2325cc7caef882ab86831eba22a712b3d7a2d8..94ccb398801946a2b81c64dd857d869b7c1b127f 100644 --- a/spec/finders/admin/runners_finder_spec.rb +++ b/spec/finders/admin/runners_finder_spec.rb @@ -37,6 +37,14 @@ end end + context 'filter by tag_name' do + it 'calls the corresponding scope on Ci::Runner' do + expect(Ci::Runner).to receive(:tagged_with).with(%w[tag1 tag2]).and_call_original + + described_class.new(params: { tag_name: %w[tag1 tag2] }).execute + end + end + context 'sort' do context 'without sort param' do it 'sorts by created_at' do diff --git a/spec/services/autocomplete_tags_service_spec.rb b/spec/services/autocomplete_tags_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ff7128baa24109070972f16a1e98f8f5190f923e --- /dev/null +++ b/spec/services/autocomplete_tags_service_spec.rb @@ -0,0 +1,17 @@ +require 'rails_helper' + +RSpec.describe AutocompleteTagsService do + describe '#run' do + it 'returns a hash of all tags' do + create(:ci_runner, tag_list: %w[tag1 tag2]) + create(:ci_runner, tag_list: %w[tag1 tag3]) + create(:project, tag_list: %w[tag4]) + + expect(described_class.new(Ci::Runner).run).to match_array [ + { id: kind_of(Integer), title: 'tag1' }, + { id: kind_of(Integer), title: 'tag2' }, + { id: kind_of(Integer), title: 'tag3' } + ] + end + end +end