diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-10 18:09:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-10 18:09:22 +0300 |
commit | 37140013714814d8ffe662a372697c56eea2fde0 (patch) | |
tree | b25c0bfc62da359f97b8b3742007c07723242f93 /app/assets/javascripts/runner | |
parent | 948023c9c900344aa1e2f334bcaae5a194873b0d (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/runner')
14 files changed, 301 insertions, 28 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 c2bb635e056..a90ef2d3530 100644 --- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -10,6 +10,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerBulkDelete from '../components/runner_bulk_delete.vue'; import RunnerList from '../components/runner_list.vue'; +import RunnerListEmptyState from '../components/runner_list_empty_state.vue'; import RunnerName from '../components/runner_name.vue'; import RunnerStats from '../components/stat/runner_stats.vue'; import RunnerPagination from '../components/runner_pagination.vue'; @@ -35,6 +36,7 @@ import { fromUrlQueryToSearch, fromSearchToUrl, fromSearchToVariables, + isSearchFiltered, } from '../runner_search_utils'; import { captureException } from '../sentry_utils'; @@ -91,6 +93,7 @@ export default { RunnerFilteredSearchBar, RunnerBulkDelete, RunnerList, + RunnerListEmptyState, RunnerName, RunnerStats, RunnerPagination, @@ -98,7 +101,7 @@ export default { RunnerActionsCell, }, mixins: [glFeatureFlagMixin()], - inject: ['localMutations'], + inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath', 'localMutations'], props: { registrationToken: { type: String, @@ -190,6 +193,9 @@ export default { // Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/353981 return this.glFeatures.adminRunnersBulkDelete; }, + isSearchFiltered() { + return isSearchFiltered(this.search); + }, }, watch: { search: { @@ -298,9 +304,13 @@ export default { :stale-runners-count="staleRunnersTotal" /> - <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> - {{ __('No runners found') }} - </div> + <runner-list-empty-state + v-if="noRunnersFound" + :registration-token="registrationToken" + :is-search-filtered="isSearchFiltered" + :svg-path="emptyStateSvgPath" + :filtered-svg-path="emptyStateFilteredSvgPath" + /> <template v-else> <runner-bulk-delete v-if="isBulkDeleteEnabled" /> <runner-list diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js index b1d8442bb32..7bb6cd5689e 100644 --- a/app/assets/javascripts/runner/admin_runners/index.js +++ b/app/assets/javascripts/runner/admin_runners/index.js @@ -34,6 +34,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { registrationToken, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, } = el.dataset; const { cacheConfig, typeDefs, localMutations } = createLocalState(); @@ -50,6 +52,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { localMutations, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, }, render(h) { return h(AdminRunnersApp, { diff --git a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue index 93f86ae2a2c..a48db9f8ac8 100644 --- a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue +++ b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue @@ -7,6 +7,8 @@ import RunnerPausedBadge from '../runner_paused_badge.vue'; export default { components: { RunnerStatusBadge, + RunnerUpgradeStatusBadge: () => + import('ee_component/runner/components/runner_upgrade_status_badge.vue'), RunnerPausedBadge, }, directives: { @@ -33,6 +35,11 @@ export default { size="sm" class="gl-display-inline-block gl-max-w-full gl-text-truncate" /> + <runner-upgrade-status-badge + :runner="runner" + size="sm" + class="gl-display-inline-block gl-max-w-full gl-text-truncate" + /> <runner-paused-badge v-if="paused" size="sm" diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index dcfd4b84dd2..f1f99c728c5 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -12,7 +12,7 @@ import RunnerStatusCell from './cells/runner_status_cell.vue'; import RunnerTags from './runner_tags.vue'; const defaultFields = [ - tableField({ key: 'status', label: s__('Runners|Status') }), + tableField({ key: 'status', label: s__('Runners|Status'), thClasses: ['gl-w-15p'] }), tableField({ key: 'summary', label: s__('Runners|Runner'), thClasses: ['gl-lg-w-25p'] }), tableField({ key: 'version', label: __('Version') }), tableField({ key: 'jobCount', label: __('Jobs') }), diff --git a/app/assets/javascripts/runner/components/runner_list_empty_state.vue b/app/assets/javascripts/runner/components/runner_list_empty_state.vue new file mode 100644 index 00000000000..cddd51a351c --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_list_empty_state.vue @@ -0,0 +1,68 @@ +<script> +import { GlEmptyState, GlLink, GlSprintf, GlModalDirective } from '@gitlab/ui'; +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; + +export default { + components: { + GlEmptyState, + GlLink, + GlSprintf, + RunnerInstructionsModal, + }, + directives: { + GlModal: GlModalDirective, + }, + props: { + isSearchFiltered: { + type: Boolean, + required: false, + default: false, + }, + svgPath: { + type: String, + required: false, + default: '', + }, + filteredSvgPath: { + type: String, + required: false, + default: '', + }, + registrationToken: { + type: String, + required: false, + default: null, + }, + }, + modalId: 'runners-empty-state-instructions-modal', +}; +</script> + +<template> + <gl-empty-state + v-if="isSearchFiltered" + :title="s__('Runners|No results found')" + :svg-path="filteredSvgPath" + :description="s__('Runners|Edit your search and try again')" + /> + <gl-empty-state v-else :title="s__('Runners|Get started with runners')" :svg-path="svgPath"> + <template #description> + <gl-sprintf + :message=" + s__( + 'Runners|Runners are the agents that run your CI/CD jobs. Follow the %{linkStart}installation and registration instructions%{linkEnd} to set up a runner.', + ) + " + > + <template #link="{ content }"> + <gl-link v-gl-modal="$options.modalId">{{ content }}</gl-link> + </template> + </gl-sprintf> + + <runner-instructions-modal + :modal-id="$options.modalId" + :registration-token="registrationToken" + /> + </template> + </gl-empty-state> +</template> diff --git a/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql index 5d0450e7418..61bfe03bf6e 100644 --- a/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/list/admin_runners.query.graphql @@ -1,4 +1,4 @@ -#import "~/runner/graphql/list/list_item.fragment.graphql" +#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql" #import "~/graphql_shared/fragments/page_info.fragment.graphql" query getRunners( diff --git a/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql index b4f2b5cd8c8..8755636a7ad 100644 --- a/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql @@ -1,4 +1,4 @@ -#import "~/runner/graphql/list/list_item.fragment.graphql" +#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql" #import "~/graphql_shared/fragments/page_info.fragment.graphql" query getGroupRunners( diff --git a/app/assets/javascripts/runner/graphql/list/list_item.fragment.graphql b/app/assets/javascripts/runner/graphql/list/list_item.fragment.graphql index 620c18c5bc0..19a5a48ea75 100644 --- a/app/assets/javascripts/runner/graphql/list/list_item.fragment.graphql +++ b/app/assets/javascripts/runner/graphql/list/list_item.fragment.graphql @@ -1,20 +1,5 @@ +#import "./list_item_shared.fragment.graphql" + fragment ListItem on CiRunner { - __typename - id - description - runnerType - shortSha - version - revision - ipAddress - active - locked - jobCount - tagList - contactedAt - status(legacyMode: null) - userPermissions { - updateRunner - deleteRunner - } + ...ListItemShared } diff --git a/app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql new file mode 100644 index 00000000000..cf925359ffb --- /dev/null +++ b/app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql @@ -0,0 +1,20 @@ +fragment ListItemShared on CiRunner { + __typename + id + description + runnerType + shortSha + version + revision + ipAddress + active + locked + jobCount + tagList + contactedAt + status(legacyMode: null) + userPermissions { + updateRunner + deleteRunner + } +} diff --git a/app/assets/javascripts/runner/group_runner_show/group_runner_show_app.vue b/app/assets/javascripts/runner/group_runner_show/group_runner_show_app.vue new file mode 100644 index 00000000000..c336e091fdf --- /dev/null +++ b/app/assets/javascripts/runner/group_runner_show/group_runner_show_app.vue @@ -0,0 +1,114 @@ +<script> +import { GlBadge, GlTab, GlTooltipDirective } from '@gitlab/ui'; +import { createAlert, VARIANT_SUCCESS } from '~/flash'; +import { TYPE_CI_RUNNER } from '~/graphql_shared/constants'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { formatJobCount } from '../utils'; +import RunnerDeleteButton from '../components/runner_delete_button.vue'; +import RunnerEditButton from '../components/runner_edit_button.vue'; +import RunnerPauseButton from '../components/runner_pause_button.vue'; +import RunnerHeader from '../components/runner_header.vue'; +import RunnerDetails from '../components/runner_details.vue'; +import RunnerJobs from '../components/runner_jobs.vue'; +import { I18N_FETCH_ERROR } from '../constants'; +import runnerQuery from '../graphql/show/runner.query.graphql'; +import { captureException } from '../sentry_utils'; +import { saveAlertToLocalStorage } from '../local_storage_alert/save_alert_to_local_storage'; + +export default { + name: 'GroupRunnerShowApp', + components: { + GlBadge, + GlTab, + RunnerDeleteButton, + RunnerEditButton, + RunnerPauseButton, + RunnerHeader, + RunnerDetails, + RunnerJobs, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + runnerId: { + type: String, + required: true, + }, + runnersPath: { + type: String, + required: true, + }, + }, + data() { + return { + runner: null, + }; + }, + apollo: { + runner: { + query: runnerQuery, + variables() { + return { + id: convertToGraphQLId(TYPE_CI_RUNNER, this.runnerId), + }; + }, + error(error) { + createAlert({ message: I18N_FETCH_ERROR }); + + this.reportToSentry(error); + }, + }, + }, + computed: { + canUpdate() { + return this.runner.userPermissions?.updateRunner; + }, + canDelete() { + return this.runner.userPermissions?.deleteRunner; + }, + jobCount() { + return formatJobCount(this.runner?.jobCount); + }, + }, + errorCaptured(error) { + this.reportToSentry(error); + }, + methods: { + reportToSentry(error) { + captureException({ error, component: this.$options.name }); + }, + onDeleted({ message }) { + saveAlertToLocalStorage({ message, variant: VARIANT_SUCCESS }); + redirectTo(this.runnersPath); + }, + }, +}; +</script> +<template> + <div> + <runner-header v-if="runner" :runner="runner"> + <template #actions> + <runner-edit-button v-if="canUpdate && runner.editAdminUrl" :href="runner.editAdminUrl" /> + <runner-pause-button v-if="canUpdate" :runner="runner" /> + <runner-delete-button v-if="canDelete" :runner="runner" @deleted="onDeleted" /> + </template> + </runner-header> + + <runner-details :runner="runner"> + <template #jobs-tab> + <gl-tab> + <template #title> + {{ s__('Runners|Jobs') }} + <gl-badge v-if="jobCount" data-testid="job-count-badge" class="gl-ml-1" size="sm"> + {{ jobCount }} + </gl-badge> + </template> + + <runner-jobs v-if="runner" :runner="runner" /> + </gl-tab> + </template> + </runner-details> + </div> +</template> diff --git a/app/assets/javascripts/runner/group_runner_show/index.js b/app/assets/javascripts/runner/group_runner_show/index.js new file mode 100644 index 00000000000..d1b87c8e427 --- /dev/null +++ b/app/assets/javascripts/runner/group_runner_show/index.js @@ -0,0 +1,36 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import { showAlertFromLocalStorage } from '../local_storage_alert/show_alert_from_local_storage'; +import GroupRunnerShowApp from './group_runner_show_app.vue'; + +Vue.use(VueApollo); + +export const initAdminRunnerShow = (selector = '#js-group-runner-show') => { + showAlertFromLocalStorage(); + + const el = document.querySelector(selector); + + if (!el) { + return null; + } + + const { runnerId, runnersPath } = el.dataset; + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + return new Vue({ + el, + apolloProvider, + render(h) { + return h(GroupRunnerShowApp, { + props: { + runnerId, + runnersPath, + }, + }); + }, + }); +}; 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 b5bd4b111fd..641b3a8f560 100644 --- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue +++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue @@ -8,6 +8,7 @@ import { fetchPolicies } from '~/lib/graphql'; import RegistrationDropdown from '../components/registration/registration_dropdown.vue'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; +import RunnerListEmptyState from '../components/runner_list_empty_state.vue'; import RunnerName from '../components/runner_name.vue'; import RunnerStats from '../components/stat/runner_stats.vue'; import RunnerPagination from '../components/runner_pagination.vue'; @@ -31,6 +32,7 @@ import { fromUrlQueryToSearch, fromSearchToUrl, fromSearchToVariables, + isSearchFiltered, } from '../runner_search_utils'; import { captureException } from '../sentry_utils'; @@ -86,12 +88,14 @@ export default { RegistrationDropdown, RunnerFilteredSearchBar, RunnerList, + RunnerListEmptyState, RunnerName, RunnerStats, RunnerPagination, RunnerTypeTabs, RunnerActionsCell, }, + inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath'], props: { registrationToken: { type: String, @@ -196,6 +200,9 @@ export default { filteredSearchNamespace() { return `${GROUP_FILTERED_SEARCH_NAMESPACE}/${this.groupFullPath}`; }, + isSearchFiltered() { + return isSearchFiltered(this.search); + }, }, watch: { search: { @@ -299,9 +306,13 @@ export default { :stale-runners-count="staleRunnersTotal" /> - <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> - {{ __('No runners found') }} - </div> + <runner-list-empty-state + v-if="noRunnersFound" + :registration-token="registrationToken" + :is-search-filtered="isSearchFiltered" + :svg-path="emptyStateSvgPath" + :filtered-svg-path="emptyStateFilteredSvgPath" + /> <template v-else> <runner-list :runners="runners.items" :loading="runnersLoading"> <template #runner-name="{ runner }"> diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js index 0dade30f820..feed6b0ceb7 100644 --- a/app/assets/javascripts/runner/group_runners/index.js +++ b/app/assets/javascripts/runner/group_runners/index.js @@ -22,6 +22,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => { groupRunnersLimitedCount, onlineContactTimeoutSecs, staleTimeoutSecs, + emptyStateSvgPath, + emptyStateFilteredSvgPath, } = el.dataset; const apolloProvider = new VueApollo({ @@ -36,6 +38,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => { groupId, onlineContactTimeoutSecs: parseInt(onlineContactTimeoutSecs, 10), staleTimeoutSecs: parseInt(staleTimeoutSecs, 10), + emptyStateSvgPath, + emptyStateFilteredSvgPath, }, render(h) { return h(GroupRunnersApp, { diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js index 0d688ed65ef..e01878f355a 100644 --- a/app/assets/javascripts/runner/runner_search_utils.js +++ b/app/assets/javascripts/runner/runner_search_utils.js @@ -236,3 +236,17 @@ export const fromSearchToVariables = ({ ...paginationVariables, }; }; + +/** + * Decides whether or not a search object is the "default" or empty. + * + * A search is filtered if the user has entered filtering criteria. + * + * @param {Object} search + * @returns true if this search is filtered, false otherwise + */ +export const isSearchFiltered = ({ runnerType = null, filters = [], pagination = {} } = {}) => { + return Boolean( + runnerType !== null || filters?.length !== 0 || (pagination && pagination?.page !== 1), + ); +}; |