diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 13:18:24 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 13:18:24 +0000 |
commit | 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch) | |
tree | 4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /app/assets/javascripts/runner | |
parent | 744144d28e3e7fddc117924fef88de5d9674fe4c (diff) | |
download | gitlab-ce-0653e08efd039a5905f3fa4f6e9cef9f5d2f799c.tar.gz |
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/runner')
12 files changed, 286 insertions, 91 deletions
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue index 23ecee449a4..fedd2519958 100644 --- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -2,12 +2,16 @@ import createFlash from '~/flash'; import { fetchPolicies } from '~/lib/graphql'; import { updateHistory } from '~/lib/utils/url_utility'; +import { formatNumber, sprintf, __ } from '~/locale'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; -import { INSTANCE_TYPE, I18N_FETCH_ERROR } from '../constants'; +import { statusTokenConfig } from '../components/search_tokens/status_token_config'; +import { tagTokenConfig } from '../components/search_tokens/tag_token_config'; +import { typeTokenConfig } from '../components/search_tokens/type_token_config'; +import { ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, I18N_FETCH_ERROR } from '../constants'; import getRunnersQuery from '../graphql/get_runners.query.graphql'; import { fromUrlQueryToSearch, @@ -78,6 +82,21 @@ export default { noRunnersFound() { return !this.runnersLoading && !this.runners.items.length; }, + activeRunnersMessage() { + return sprintf(__('Runners currently online: %{active_runners_count}'), { + active_runners_count: formatNumber(this.activeRunnersCount), + }); + }, + searchTokens() { + return [ + statusTokenConfig, + typeTokenConfig, + { + ...tagTokenConfig, + recentTokenValuesStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`, + }, + ]; + }, }, watch: { search: { @@ -99,6 +118,7 @@ export default { captureException({ error, component: this.$options.name }); }, }, + filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE, INSTANCE_TYPE, }; </script> @@ -118,9 +138,13 @@ export default { <runner-filtered-search-bar v-model="search" - namespace="admin_runners" - :active-runners-count="activeRunnersCount" - /> + :tokens="searchTokens" + :namespace="$options.filteredSearchNamespace" + > + <template #runner-count> + {{ activeRunnersMessage }} + </template> + </runner-filtered-search-bar> <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> {{ __('No runners found') }} diff --git a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue index e14b3b17fa8..e04ca8ddca0 100644 --- a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue +++ b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue @@ -1,27 +1,8 @@ <script> import { cloneDeep } from 'lodash'; -import { formatNumber, sprintf, __, s__ } from '~/locale'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { __ } from '~/locale'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; -import { - STATUS_ACTIVE, - STATUS_PAUSED, - STATUS_ONLINE, - STATUS_OFFLINE, - STATUS_NOT_CONNECTED, - INSTANCE_TYPE, - GROUP_TYPE, - PROJECT_TYPE, - CREATED_DESC, - CREATED_ASC, - CONTACTED_DESC, - CONTACTED_ASC, - PARAM_KEY_STATUS, - PARAM_KEY_RUNNER_TYPE, - PARAM_KEY_TAG, -} from '../constants'; -import TagToken from './search_tokens/tag_token.vue'; +import { CREATED_DESC, CREATED_ASC, CONTACTED_DESC, CONTACTED_ASC } from '../constants'; const sortOptions = [ { @@ -58,10 +39,6 @@ export default { type: String, required: true, }, - activeRunnersCount: { - type: Number, - required: true, - }, }, data() { // filtered_search_bar_root.vue may mutate the inital @@ -73,62 +50,6 @@ export default { initialSortBy: sort, }; }, - computed: { - searchTokens() { - return [ - { - icon: 'status', - title: __('Status'), - type: PARAM_KEY_STATUS, - token: BaseToken, - unique: true, - options: [ - { value: STATUS_ACTIVE, title: s__('Runners|Active') }, - { value: STATUS_PAUSED, title: s__('Runners|Paused') }, - { value: STATUS_ONLINE, title: s__('Runners|Online') }, - { value: STATUS_OFFLINE, title: s__('Runners|Offline') }, - - // Added extra quotes in this title to avoid splitting this value: - // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438 - { value: STATUS_NOT_CONNECTED, title: `"${s__('Runners|Not connected')}"` }, - ], - // TODO In principle we could support more complex search rules, - // this can be added to a separate issue. - operators: OPERATOR_IS_ONLY, - }, - - { - icon: 'file-tree', - title: __('Type'), - type: PARAM_KEY_RUNNER_TYPE, - token: BaseToken, - unique: true, - options: [ - { value: INSTANCE_TYPE, title: s__('Runners|instance') }, - { value: GROUP_TYPE, title: s__('Runners|group') }, - { value: PROJECT_TYPE, title: s__('Runners|project') }, - ], - // TODO We should support more complex search rules, - // search for multiple states (OR) or have NOT operators - operators: OPERATOR_IS_ONLY, - }, - - { - icon: 'tag', - title: s__('Runners|Tags'), - type: PARAM_KEY_TAG, - token: TagToken, - recentTokenValuesStorageKey: `${this.namespace}-recent-tags`, - operators: OPERATOR_IS_ONLY, - }, - ]; - }, - activeRunnersMessage() { - return sprintf(__('Runners currently online: %{active_runners_count}'), { - active_runners_count: formatNumber(this.activeRunnersCount), - }); - }, - }, methods: { onFilter(filters) { const { sort } = this.value; @@ -161,12 +82,13 @@ export default { :sort-options="$options.sortOptions" :initial-filter-value="initialFilterValue" :initial-sort-by="initialSortBy" - :tokens="searchTokens" :search-input-placeholder="__('Search or filter results...')" data-testid="runners-filtered-search" @onFilter="onFilter" @onSort="onSort" /> - <div class="gl-text-right" data-testid="active-runners-message">{{ activeRunnersMessage }}</div> + <div class="gl-text-right" data-testid="runner-count"> + <slot name="runner-count"></slot> + </div> </div> </template> diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue index a5bc1680852..9a6fc07f6dd 100644 --- a/app/assets/javascripts/runner/components/runner_update_form.vue +++ b/app/assets/javascripts/runner/components/runner_update_form.vue @@ -135,9 +135,9 @@ export default { </gl-form-checkbox> <gl-form-checkbox + v-if="canBeLockedToProject" v-model="model.locked" data-testid="runner-field-locked" - :disabled="!canBeLockedToProject" > {{ __('Lock to current projects') }} <template #help> diff --git a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js new file mode 100644 index 00000000000..03dff5e61a5 --- /dev/null +++ b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js @@ -0,0 +1,32 @@ +import { __, s__ } from '~/locale'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; +import { + STATUS_ACTIVE, + STATUS_PAUSED, + STATUS_ONLINE, + STATUS_OFFLINE, + STATUS_NOT_CONNECTED, + PARAM_KEY_STATUS, +} from '../../constants'; + +export const statusTokenConfig = { + icon: 'status', + title: __('Status'), + type: PARAM_KEY_STATUS, + token: BaseToken, + unique: true, + options: [ + { value: STATUS_ACTIVE, title: s__('Runners|Active') }, + { value: STATUS_PAUSED, title: s__('Runners|Paused') }, + { value: STATUS_ONLINE, title: s__('Runners|Online') }, + { value: STATUS_OFFLINE, title: s__('Runners|Offline') }, + + // Added extra quotes in this title to avoid splitting this value: + // see: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1438 + { value: STATUS_NOT_CONNECTED, title: `"${s__('Runners|Not connected')}"` }, + ], + // TODO In principle we could support more complex search rules, + // this can be added to a separate issue. + operators: OPERATOR_IS_ONLY, +}; diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue index 51fae60b6b7..ab67ac608e2 100644 --- a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue +++ b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue @@ -33,6 +33,7 @@ export default { // The API should // 1) scope to the rights of the user // 2) stay up to date to the removal of old tags + // 3) consider the scope of search, like searching within the tags of a group // See: https://gitlab.com/gitlab-org/gitlab/-/issues/333796 return axios .get(TAG_SUGGESTIONS_PATH, { diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token_config.js b/app/assets/javascripts/runner/components/search_tokens/tag_token_config.js new file mode 100644 index 00000000000..fdeba714385 --- /dev/null +++ b/app/assets/javascripts/runner/components/search_tokens/tag_token_config.js @@ -0,0 +1,12 @@ +import { s__ } from '~/locale'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { PARAM_KEY_TAG } from '../../constants'; +import TagToken from './tag_token.vue'; + +export const tagTokenConfig = { + icon: 'tag', + title: s__('Runners|Tags'), + type: PARAM_KEY_TAG, + token: TagToken, + operators: OPERATOR_IS_ONLY, +}; diff --git a/app/assets/javascripts/runner/components/search_tokens/type_token_config.js b/app/assets/javascripts/runner/components/search_tokens/type_token_config.js new file mode 100644 index 00000000000..1da61c53386 --- /dev/null +++ b/app/assets/javascripts/runner/components/search_tokens/type_token_config.js @@ -0,0 +1,20 @@ +import { __, s__ } from '~/locale'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; +import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, PARAM_KEY_RUNNER_TYPE } from '../../constants'; + +export const typeTokenConfig = { + icon: 'file-tree', + title: __('Type'), + type: PARAM_KEY_RUNNER_TYPE, + token: BaseToken, + unique: true, + options: [ + { value: INSTANCE_TYPE, title: s__('Runners|instance') }, + { value: GROUP_TYPE, title: s__('Runners|group') }, + { value: PROJECT_TYPE, title: s__('Runners|project') }, + ], + // TODO We should support more complex search rules, + // search for multiple states (OR) or have NOT operators + operators: OPERATOR_IS_ONLY, +}; diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js index 2822882e0cc..46e55b322c7 100644 --- a/app/assets/javascripts/runner/constants.js +++ b/app/assets/javascripts/runner/constants.js @@ -2,6 +2,7 @@ import { s__ } from '~/locale'; export const RUNNER_PAGE_SIZE = 20; export const RUNNER_JOB_COUNT_LIMIT = 1000; +export const GROUP_RUNNER_COUNT_LIMIT = 1000; export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.'); export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}'); @@ -50,3 +51,8 @@ export const CONTACTED_DESC = 'CONTACTED_DESC'; // TODO Add this to the API export const CONTACTED_ASC = 'CONTACTED_ASC'; export const DEFAULT_SORT = CREATED_DESC; + +// Local storage namespaces + +export const ADMIN_FILTERED_SEARCH_NAMESPACE = 'admin_runners'; +export const GROUP_FILTERED_SEARCH_NAMESPACE = 'group_runners'; diff --git a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql new file mode 100644 index 00000000000..a601ee8d611 --- /dev/null +++ b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql @@ -0,0 +1,35 @@ +#import "~/runner/graphql/runner_node.fragment.graphql" +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getGroupRunners( + $groupFullPath: ID! + $before: String + $after: String + $first: Int + $last: Int + $status: CiRunnerStatus + $type: CiRunnerType + $search: String + $sort: CiRunnerSort +) { + group(fullPath: $groupFullPath) { + runners( + membership: DESCENDANTS + before: $before + after: $after + first: $first + last: $last + status: $status + type: $type + search: $search + sort: $sort + ) { + nodes { + ...RunnerNode + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue index 07bbf60c453..42e1a9e1de9 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -1,18 +1,135 @@ <script> +import createFlash from '~/flash'; +import { fetchPolicies } from '~/lib/graphql'; +import { updateHistory } from '~/lib/utils/url_utility'; +import { formatNumber, sprintf, s__ } from '~/locale'; +import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; +import RunnerList from '../components/runner_list.vue'; import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; +import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; -import { GROUP_TYPE } from '../constants'; +import { statusTokenConfig } from '../components/search_tokens/status_token_config'; +import { typeTokenConfig } from '../components/search_tokens/type_token_config'; +import { + I18N_FETCH_ERROR, + GROUP_FILTERED_SEARCH_NAMESPACE, + GROUP_TYPE, + GROUP_RUNNER_COUNT_LIMIT, +} from '../constants'; +import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql'; +import { + fromUrlQueryToSearch, + fromSearchToUrl, + fromSearchToVariables, +} from '../runner_search_utils'; +import { captureException } from '../sentry_utils'; export default { + name: 'GroupRunnersApp', components: { + RunnerFilteredSearchBar, + RunnerList, RunnerManualSetupHelp, RunnerTypeHelp, + RunnerPagination, }, props: { registrationToken: { type: String, required: true, }, + groupFullPath: { + type: String, + required: true, + }, + groupRunnersLimitedCount: { + type: Number, + required: true, + }, + }, + data() { + return { + search: fromUrlQueryToSearch(), + runners: { + items: [], + pageInfo: {}, + }, + }; + }, + apollo: { + runners: { + query: getGroupRunnersQuery, + // Runners can be updated by users directly in this list. + // A "cache and network" policy prevents outdated filtered + // results. + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + variables() { + return this.variables; + }, + update(data) { + const { runners } = data?.group || {}; + return { + items: runners?.nodes || [], + pageInfo: runners?.pageInfo || {}, + }; + }, + error(error) { + createFlash({ message: I18N_FETCH_ERROR }); + + this.reportToSentry(error); + }, + }, + }, + computed: { + variables() { + return { + ...fromSearchToVariables(this.search), + groupFullPath: this.groupFullPath, + }; + }, + runnersLoading() { + return this.$apollo.queries.runners.loading; + }, + noRunnersFound() { + return !this.runnersLoading && !this.runners.items.length; + }, + groupRunnersCount() { + if (this.groupRunnersLimitedCount > GROUP_RUNNER_COUNT_LIMIT) { + return `${formatNumber(GROUP_RUNNER_COUNT_LIMIT)}+`; + } + return formatNumber(this.groupRunnersLimitedCount); + }, + runnerCountMessage() { + return sprintf(s__('Runners|Runners in this group: %{groupRunnersCount}'), { + groupRunnersCount: this.groupRunnersCount, + }); + }, + searchTokens() { + return [statusTokenConfig, typeTokenConfig]; + }, + filteredSearchNamespace() { + return `${GROUP_FILTERED_SEARCH_NAMESPACE}/${this.groupFullPath}`; + }, + }, + watch: { + search: { + deep: true, + handler() { + // TODO Implement back button reponse using onpopstate + updateHistory({ + url: fromSearchToUrl(this.search), + title: document.title, + }); + }, + }, + }, + errorCaptured(error) { + this.reportToSentry(error); + }, + methods: { + reportToSentry(error) { + captureException({ error, component: this.$options.name }); + }, }, GROUP_TYPE, }; @@ -31,5 +148,23 @@ export default { /> </div> </div> + + <runner-filtered-search-bar + v-model="search" + :tokens="searchTokens" + :namespace="filteredSearchNamespace" + > + <template #runner-count> + {{ runnerCountMessage }} + </template> + </runner-filtered-search-bar> + + <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> + {{ __('No runners found') }} + </div> + <template v-else> + <runner-list :runners="runners.items" :loading="runnersLoading" /> + <runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" /> + </template> </div> </template> diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js index e14c583d73e..9545764c68d 100644 --- a/app/assets/javascripts/runner/group_runners/index.js +++ b/app/assets/javascripts/runner/group_runners/index.js @@ -12,7 +12,13 @@ export const initGroupRunners = (selector = '#js-group-runners') => { return null; } - const { registrationToken, groupId } = el.dataset; + const { + registrationToken, + runnerInstallHelpPage, + groupId, + groupFullPath, + groupRunnersLimitedCount, + } = el.dataset; const apolloProvider = new VueApollo({ defaultClient: createDefaultClient( @@ -27,12 +33,15 @@ export const initGroupRunners = (selector = '#js-group-runners') => { el, apolloProvider, provide: { + runnerInstallHelpPage, groupId, }, render(h) { return h(GroupRunnersApp, { props: { registrationToken, + groupFullPath, + groupRunnersLimitedCount: parseInt(groupRunnersLimitedCount, 10), }, }); }, diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js index 65f75eb11ac..0a817ea0acf 100644 --- a/app/assets/javascripts/runner/runner_search_utils.js +++ b/app/assets/javascripts/runner/runner_search_utils.js @@ -43,7 +43,6 @@ export const fromUrlQueryToSearch = (query = window.location.search) => { urlQueryToFilter(query, { filterNamesAllowList: [PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_TAG], filteredSearchTermKey: PARAM_KEY_SEARCH, - legacySpacesDecode: false, }), ), sort: params[PARAM_KEY_SORT] || DEFAULT_SORT, |