From 9ee4966a5ff4c15ff7df561b06393c673e1c4456 Mon Sep 17 00:00:00 2001 From: Joseph Snyder Date: Fri, 17 Oct 2025 15:02:26 -0400 Subject: [PATCH 1/3] Filter runners on admin page by owning Group This adds a filtering option on the Admin runners dashboard to filter runners by the owning group. For organizations with many runners, this is a usability improvement for this page. This is merge request 1 of 3. Co-authored-by: Martin Duffy --- .../components/runner_filtered_search_bar.vue | 8 + .../search_tokens/group_token_config.js | 12 + app/assets/javascripts/ci/runner/constants.js | 2 + .../graphql/list/all_runners.query.graphql | 2 + .../list/all_runners_count.query.graphql | 2 + .../ci/runner/runner_search_utils.js | 4 + .../queries/groups_autocomplete.query.graphql | 1 + .../filtered_search_bar_root.vue | 8 + .../tokens/group_token.vue | 22 +- .../graphql/list/all_runners.query.graphql | 2 + .../list/all_runners_count.query.graphql | 2 + .../runner_filtered_search_bar_spec.js | 32 +++ .../filtered_search_bar_root_spec.js | 36 +++ .../filtered_search_bar/mock_data.js | 36 +++ .../tokens/group_token_spec.js | 221 ++++++++++++++++++ .../components/list_selector/index_spec.js | 1 + .../components/list_selector/mock_data.js | 2 + 17 files changed, 386 insertions(+), 7 deletions(-) create mode 100644 app/assets/javascripts/ci/runner/components/search_tokens/group_token_config.js create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/group_token_spec.js diff --git a/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue b/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue index 18bb9e62a3b272..1e29ddb4a45eb6 100644 --- a/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue +++ b/app/assets/javascripts/ci/runner/components/runner_filtered_search_bar.vue @@ -79,6 +79,12 @@ export default { pagination: {}, }); }, + onTokenComplete(token) { + this.$emit('token-complete', token); + }, + onTokenDestroy(token) { + this.$emit('token-destroy', token); + }, }, sortOptions, }; @@ -97,5 +103,7 @@ export default { data-testid="runners-filtered-search" @onFilter="onFilter" @onSort="onSort" + @token-complete="onTokenComplete" + @token-destroy="onTokenDestroy" /> diff --git a/app/assets/javascripts/ci/runner/components/search_tokens/group_token_config.js b/app/assets/javascripts/ci/runner/components/search_tokens/group_token_config.js new file mode 100644 index 00000000000000..e442acf1555524 --- /dev/null +++ b/app/assets/javascripts/ci/runner/components/search_tokens/group_token_config.js @@ -0,0 +1,12 @@ +import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; +import GroupToken from '~/vue_shared/components/filtered_search_bar/tokens/group_token.vue'; +import { PARAM_KEY_GROUP, I18N_GROUP } from '~/ci/runner/constants'; + +export const groupTokenConfig = { + icon: 'group', + title: I18N_GROUP, + type: PARAM_KEY_GROUP, + token: GroupToken, + operators: OPERATORS_IS, + unique: true, +}; diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js index 597d31f9015482..29f3f8870d5b6e 100644 --- a/app/assets/javascripts/ci/runner/constants.js +++ b/app/assets/javascripts/ci/runner/constants.js @@ -75,6 +75,7 @@ export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted'); // List export const I18N_CREATOR = s__('Runners|Creator'); +export const I18N_GROUP = s__('Runners|Group'); export const I18N_LOCKED_RUNNER_DESCRIPTION = s__( 'Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects.', ); @@ -122,6 +123,7 @@ export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100'; // - GlFilteredSearch tokens type export const PARAM_KEY_CREATOR = 'creator'; +export const PARAM_KEY_GROUP = 'group'; export const PARAM_KEY_STATUS = 'status'; export const PARAM_KEY_PAUSED = 'paused'; export const PARAM_KEY_RUNNER_TYPE = 'runner_type'; diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql index b11fb74b3bb46d..18e26b6014fed1 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners.query.graphql @@ -12,6 +12,7 @@ query getAllRunners( $search: String $versionPrefix: String $creator: String + $ownerFullPath: String $sort: CiRunnerSort ) { runners( @@ -26,6 +27,7 @@ query getAllRunners( search: $search versionPrefix: $versionPrefix creatorUsername: $creator + ownerFullPath: $ownerFullPath sort: $sort ) { ...AllRunnersConnection diff --git a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql index c6f59b404bdecd..7d55e07526dbca 100644 --- a/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql +++ b/app/assets/javascripts/ci/runner/graphql/list/all_runners_count.query.graphql @@ -6,6 +6,7 @@ query getAllRunnersCount( $search: String $versionPrefix: String $creator: String + $ownerFullPath: String ) { runners( paused: $paused @@ -15,6 +16,7 @@ query getAllRunnersCount( search: $search versionPrefix: $versionPrefix creatorUsername: $creator + ownerFullPath: $ownerFullPath ) { count } diff --git a/app/assets/javascripts/ci/runner/runner_search_utils.js b/app/assets/javascripts/ci/runner/runner_search_utils.js index e92d57b45fc69b..ae117d23ccaefa 100644 --- a/app/assets/javascripts/ci/runner/runner_search_utils.js +++ b/app/assets/javascripts/ci/runner/runner_search_utils.js @@ -15,6 +15,7 @@ import { PARAM_KEY_VERSION, PARAM_KEY_SEARCH, PARAM_KEY_CREATOR, + PARAM_KEY_GROUP, PARAM_KEY_MEMBERSHIP, PARAM_KEY_SORT, PARAM_KEY_AFTER, @@ -159,6 +160,7 @@ export const fromUrlQueryToSearch = (query = window.location.search) => { PARAM_KEY_TAG, PARAM_KEY_VERSION, PARAM_KEY_CREATOR, + PARAM_KEY_GROUP, ], filteredSearchTermKey: PARAM_KEY_SEARCH, }), @@ -188,6 +190,7 @@ export const fromSearchToUrl = ( [PARAM_KEY_PAUSED]: [], [PARAM_KEY_VERSION]: [], [PARAM_KEY_CREATOR]: [], + [PARAM_KEY_GROUP]: [], // Current filters ...filterToQueryObject(processFilters(filters), { filteredSearchTermKey: PARAM_KEY_SEARCH, @@ -244,6 +247,7 @@ export const fromSearchToVariables = ({ filterVariables.tagList = queryObj[PARAM_KEY_TAG]; [filterVariables.versionPrefix] = queryObj[PARAM_KEY_VERSION] || []; [filterVariables.creator] = queryObj[PARAM_KEY_CREATOR] || []; + [filterVariables.ownerFullPath] = queryObj[PARAM_KEY_GROUP] || []; if (queryObj[PARAM_KEY_PAUSED]) { filterVariables.paused = parseBoolean(queryObj[PARAM_KEY_PAUSED]); diff --git a/app/assets/javascripts/graphql_shared/queries/groups_autocomplete.query.graphql b/app/assets/javascripts/graphql_shared/queries/groups_autocomplete.query.graphql index 74da46e5a606b7..e1e711846fddd7 100644 --- a/app/assets/javascripts/graphql_shared/queries/groups_autocomplete.query.graphql +++ b/app/assets/javascripts/graphql_shared/queries/groups_autocomplete.query.graphql @@ -3,6 +3,7 @@ query groupsAutocomplete($search: String) { nodes { id name + fullPath fullName avatarUrl } diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue index 915ae93108761a..02020827389dd0 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue @@ -346,6 +346,12 @@ export default { onInput(tokens) { this.$emit('onInput', this.removeQuotesEnclosure(uniqueTokens(tokens))); }, + onTokenComplete(token) { + this.$emit('token-complete', token); + }, + onTokenDestroy(token) { + this.$emit('token-destroy', token); + }, }, }; @@ -385,6 +391,8 @@ export default { @clear-history="handleClearHistory" @submit="handleFilterSubmit" @input="onInput" + @token-complete="onTokenComplete" + @token-destroy="onTokenDestroy" >