Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-03-31 00:09:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-03-31 00:09:29 +0300
commit89245154567c6ea01821606f3127bef766462d5e (patch)
tree2fa9adfe5c3bad9deadcbf80b07c0a1da821d060 /app/assets/javascripts/runner
parentf06ebebadece98495408c29112abf1b65a43ecb6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/runner')
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue24
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js8
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete.vue111
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue76
-rw-r--r--app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql3
-rw-r--r--app/assets/javascripts/runner/graphql/list/local_state.js63
-rw-r--r--app/assets/javascripts/runner/graphql/list/typedefs.graphql3
-rw-r--r--app/assets/javascripts/runner/utils.js3
8 files changed, 277 insertions, 14 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 9abd45424e7..3853a7d8666 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -4,9 +4,11 @@ import { createAlert } from '~/flash';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber } from '~/locale';
import { fetchPolicies } from '~/lib/graphql';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
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 RunnerName from '../components/runner_name.vue';
import RunnerStats from '../components/stat/runner_stats.vue';
@@ -53,6 +55,7 @@ export default {
GlLink,
RegistrationDropdown,
RunnerFilteredSearchBar,
+ RunnerBulkDelete,
RunnerList,
RunnerName,
RunnerStats,
@@ -60,6 +63,8 @@ export default {
RunnerTypeTabs,
RunnerActionsCell,
},
+ mixins: [glFeatureFlagMixin()],
+ inject: ['localMutations'],
props: {
registrationToken: {
type: String,
@@ -180,6 +185,11 @@ export default {
},
];
},
+ isBulkDeleteEnabled() {
+ // Feature flag: admin_runners_bulk_delete
+ // Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+ return this.glFeatures.adminRunnersBulkDelete;
+ },
},
watch: {
search: {
@@ -238,6 +248,12 @@ export default {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
+ onChecked({ runner, isChecked }) {
+ this.localMutations.setRunnerChecked({
+ runner,
+ isChecked,
+ });
+ },
},
filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE,
INSTANCE_TYPE,
@@ -286,7 +302,13 @@ export default {
{{ __('No runners found') }}
</div>
<template v-else>
- <runner-list :runners="runners.items" :loading="runnersLoading">
+ <runner-bulk-delete v-if="isBulkDeleteEnabled" />
+ <runner-list
+ :runners="runners.items"
+ :loading="runnersLoading"
+ :checkable="isBulkDeleteEnabled"
+ @checked="onChecked"
+ >
<template #runner-name="{ runner }">
<gl-link :href="runner.adminUrl">
<runner-name :runner="runner" />
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 3b8a8fe9cd1..2405bab7957 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -1,9 +1,10 @@
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
import { visitUrl } from '~/lib/utils/url_utility';
import { updateOutdatedUrl } from '~/runner/runner_search_utils';
+import createDefaultClient from '~/lib/graphql';
+import { createLocalState } from '../graphql/list/local_state';
import AdminRunnersApp from './admin_runners_app.vue';
Vue.use(GlToast);
@@ -27,8 +28,10 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
const { runnerInstallHelpPage, registrationToken } = el.dataset;
+ const { cacheConfig, typeDefs, localMutations } = createLocalState();
+
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { cacheConfig, typeDefs }),
});
return new Vue({
@@ -36,6 +39,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
apolloProvider,
provide: {
runnerInstallHelpPage,
+ localMutations,
},
render(h) {
return h(AdminRunnersApp, {
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
new file mode 100644
index 00000000000..50791de0bda
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlButton, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import { n__, sprintf } from '~/locale';
+import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
+
+export default {
+ components: {
+ GlButton,
+ GlSprintf,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ inject: ['localMutations'],
+ data() {
+ return {
+ checkedRunnerIds: [],
+ };
+ },
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ },
+ },
+ computed: {
+ checkedCount() {
+ return this.checkedRunnerIds.length || 0;
+ },
+ bannerMessage() {
+ return sprintf(
+ n__(
+ 'Runners|%{strongStart}%{count}%{strongEnd} runner selected',
+ 'Runners|%{strongStart}%{count}%{strongEnd} runners selected',
+ this.checkedCount,
+ ),
+ {
+ count: this.checkedCount,
+ },
+ );
+ },
+ modalTitle() {
+ return n__('Runners|Delete %d runner', 'Runners|Delete %d runners', this.checkedCount);
+ },
+ modalHtmlMessage() {
+ return sprintf(
+ n__(
+ 'Runners|%{strongStart}%{count}%{strongEnd} runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?',
+ 'Runners|%{strongStart}%{count}%{strongEnd} runners will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?',
+ this.checkedCount,
+ ),
+ {
+ strongStart: '<strong>',
+ strongEnd: '</strong>',
+ count: this.checkedCount,
+ },
+ false,
+ );
+ },
+ primaryBtnText() {
+ return n__(
+ 'Runners|Permanently delete %d runner',
+ 'Runners|Permanently delete %d runners',
+ this.checkedCount,
+ );
+ },
+ },
+ methods: {
+ onClearChecked() {
+ this.localMutations.clearChecked();
+ },
+ onClickDelete: ignoreWhilePending(async function onClickDelete() {
+ const confirmed = await confirmAction(null, {
+ title: this.modalTitle,
+ modalHtmlMessage: this.modalHtmlMessage,
+ primaryBtnVariant: 'danger',
+ primaryBtnText: this.primaryBtnText,
+ });
+
+ if (confirmed) {
+ // TODO Call $apollo.mutate with list of runner
+ // ids in `this.checkedRunnerIds`.
+ // See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
+ }
+ }),
+ },
+};
+</script>
+
+<template>
+ <div v-if="checkedCount" class="gl-my-4 gl-p-4 gl-border-1 gl-border-solid gl-border-gray-100">
+ <div class="gl-display-flex gl-align-items-center">
+ <div>
+ <gl-sprintf :message="bannerMessage">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </div>
+ <div class="gl-ml-auto">
+ <gl-button data-testid="clear-btn" variant="default" @click="onClearChecked">{{
+ s__('Runners|Clear selection')
+ }}</gl-button>
+ <gl-button data-testid="delete-btn" variant="danger" @click="onClickDelete">{{
+ s__('Runners|Delete selected')
+ }}</gl-button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index 819b6951e13..05cd1879d9d 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -4,11 +4,22 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/toolt
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import { formatJobCount, tableField } from '../utils';
import RunnerSummaryCell from './cells/runner_summary_cell.vue';
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: 'summary', label: s__('Runners|Runner'), thClasses: ['gl-lg-w-25p'] }),
+ tableField({ key: 'version', label: __('Version') }),
+ tableField({ key: 'jobCount', label: __('Jobs') }),
+ tableField({ key: 'tagList', label: __('Tags'), thClasses: ['gl-lg-w-25p'] }),
+ tableField({ key: 'contactedAt', label: __('Last contact') }),
+ tableField({ key: 'actions', label: '' }),
+];
+
export default {
components: {
GlTableLite,
@@ -22,7 +33,20 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ skip() {
+ return !this.checkable;
+ },
+ },
+ },
props: {
+ checkable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
loading: {
type: Boolean,
required: false,
@@ -33,6 +57,10 @@ export default {
required: true,
},
},
+ emits: ['checked'],
+ data() {
+ return { checkedRunnerIds: [] };
+ },
computed: {
tableClass() {
// <gl-table-lite> does not provide a busy state, add
@@ -42,6 +70,18 @@ export default {
'gl-opacity-6': this.loading,
};
},
+ fields() {
+ if (this.checkable) {
+ const checkboxField = tableField({
+ key: 'checkbox',
+ label: s__('Runners|Checkbox'),
+ thClasses: ['gl-w-9'],
+ tdClass: ['gl-text-center'],
+ });
+ return [checkboxField, ...defaultFields];
+ }
+ return defaultFields;
+ },
},
methods: {
formatJobCount(jobCount) {
@@ -55,16 +95,16 @@ export default {
}
return {};
},
+ onCheckboxChange(runner, isChecked) {
+ this.$emit('checked', {
+ runner,
+ isChecked,
+ });
+ },
+ isChecked(runner) {
+ return this.checkedRunnerIds.includes(runner.id);
+ },
},
- fields: [
- tableField({ key: 'status', label: s__('Runners|Status') }),
- tableField({ key: 'summary', label: s__('Runners|Runner'), thClasses: ['gl-lg-w-25p'] }),
- tableField({ key: 'version', label: __('Version') }),
- tableField({ key: 'jobCount', label: __('Jobs') }),
- tableField({ key: 'tagList', label: __('Tags'), thClasses: ['gl-lg-w-25p'] }),
- tableField({ key: 'contactedAt', label: __('Last contact') }),
- tableField({ key: 'actions', label: '' }),
- ],
};
</script>
<template>
@@ -73,13 +113,29 @@ export default {
:aria-busy="loading"
:class="tableClass"
:items="runners"
- :fields="$options.fields"
+ :fields="fields"
:tbody-tr-attr="runnerTrAttr"
data-testid="runner-list"
stacked="md"
primary-key="id"
fixed
>
+ <template #head(checkbox)>
+ <!--
+ Checkbox to select all to be added here
+ See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
+ -->
+ <span></span>
+ </template>
+
+ <template #cell(checkbox)="{ item }">
+ <input
+ type="checkbox"
+ :checked="isChecked(item)"
+ @change="onCheckboxChange(item, $event.target.checked)"
+ />
+ </template>
+
<template #cell(status)="{ item }">
<runner-status-cell :runner="item" />
</template>
diff --git a/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
new file mode 100644
index 00000000000..c01f1edb451
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/checked_runner_ids.query.graphql
@@ -0,0 +1,3 @@
+query getCheckedRunnerIds {
+ checkedRunnerIds @client
+}
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
new file mode 100644
index 00000000000..e87bc72c86a
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -0,0 +1,63 @@
+import { makeVar } from '@apollo/client/core';
+import typeDefs from './typedefs.graphql';
+
+/**
+ * Local state for checkable runner items.
+ *
+ * Usage:
+ *
+ * ```
+ * import { createLocalState } from '~/runner/graphql/list/local_state';
+ *
+ * // initialize local state
+ * const { cacheConfig, typeDefs, localMutations } = createLocalState();
+ *
+ * // configure the client
+ * apolloClient = createApolloClient({}, { cacheConfig, typeDefs });
+ *
+ * // modify local state
+ * localMutations.setRunnerChecked( ... )
+ * ```
+ *
+ * Note: Currently only in use behind a feature flag:
+ * admin_runners_bulk_delete for the admin list, rollout issue:
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/353981
+ *
+ * @returns {Object} An object to configure an Apollo client:
+ * contains cacheConfig, typeDefs, localMutations.
+ */
+export const createLocalState = () => {
+ const checkedRunnerIdsVar = makeVar({});
+
+ const cacheConfig = {
+ typePolicies: {
+ Query: {
+ fields: {
+ checkedRunnerIds() {
+ return Object.entries(checkedRunnerIdsVar())
+ .filter(([, isChecked]) => isChecked)
+ .map(([key]) => key);
+ },
+ },
+ },
+ },
+ };
+
+ const localMutations = {
+ setRunnerChecked({ runner, isChecked }) {
+ checkedRunnerIdsVar({
+ ...checkedRunnerIdsVar(),
+ [runner.id]: isChecked,
+ });
+ },
+ clearChecked() {
+ checkedRunnerIdsVar({});
+ },
+ };
+
+ return {
+ cacheConfig,
+ typeDefs,
+ localMutations,
+ };
+};
diff --git a/app/assets/javascripts/runner/graphql/list/typedefs.graphql b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
new file mode 100644
index 00000000000..24e9e20cc8c
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/typedefs.graphql
@@ -0,0 +1,3 @@
+extend type Query {
+ checkedRunnerIds: [ID!]!
+}
diff --git a/app/assets/javascripts/runner/utils.js b/app/assets/javascripts/runner/utils.js
index 6e4c8c45e7b..1f7794720de 100644
--- a/app/assets/javascripts/runner/utils.js
+++ b/app/assets/javascripts/runner/utils.js
@@ -24,7 +24,7 @@ export const formatJobCount = (jobCount) => {
* @param {Object} options
* @returns Field object to add to GlTable fields
*/
-export const tableField = ({ key, label = '', thClasses = [] }) => {
+export const tableField = ({ key, label = '', thClasses = [], ...options }) => {
return {
key,
label,
@@ -32,6 +32,7 @@ export const tableField = ({ key, label = '', thClasses = [] }) => {
tdAttr: {
'data-testid': `td-${key}`,
},
+ ...options,
};
};