summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/runner
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/runner')
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue34
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_summary_cell.vue6
-rw-r--r--app/assets/javascripts/runner/components/runner_assigned_item.vue22
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete.vue149
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue59
-rw-r--r--app/assets/javascripts/runner/components/runner_detail.vue9
-rw-r--r--app/assets/javascripts/runner/components/runner_details.vue25
-rw-r--r--app/assets/javascripts/runner/components/runner_filtered_search_bar.vue8
-rw-r--r--app/assets/javascripts/runner/components/runner_jobs.vue11
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue15
-rw-r--r--app/assets/javascripts/runner/components/runner_pagination.vue67
-rw-r--r--app/assets/javascripts/runner/components/runner_projects.vue22
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_count.vue7
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_single_stat.vue41
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_stats.vue74
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_status_stat.vue65
-rw-r--r--app/assets/javascripts/runner/constants.js3
-rw-r--r--app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql1
-rw-r--r--app/assets/javascripts/runner/graphql/list/all_runners.query.graphql13
-rw-r--r--app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql13
-rw-r--r--app/assets/javascripts/runner/graphql/list/bulk_runner_delete.mutation.graphql6
-rw-r--r--app/assets/javascripts/runner/graphql/list/group_runner_connection.fragment.graphql16
-rw-r--r--app/assets/javascripts/runner/graphql/list/group_runners.query.graphql16
-rw-r--r--app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/list/local_state.js18
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_details_shared.fragment.graphql1
-rw-r--r--app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql4
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue32
-rw-r--r--app/assets/javascripts/runner/runner_search_utils.js71
29 files changed, 518 insertions, 292 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 f6b7a8b46d7..777a332333d 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -12,10 +12,12 @@ import {
isSearchFiltered,
} from 'ee_else_ce/runner/runner_search_utils';
import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.graphql';
+import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql';
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 RunnerBulkDeleteCheckbox from '../components/runner_bulk_delete_checkbox.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerListEmptyState from '../components/runner_list_empty_state.vue';
import RunnerName from '../components/runner_name.vue';
@@ -37,6 +39,7 @@ export default {
RegistrationDropdown,
RunnerFilteredSearchBar,
RunnerBulkDelete,
+ RunnerBulkDeleteCheckbox,
RunnerList,
RunnerListEmptyState,
RunnerName,
@@ -138,11 +141,15 @@ export default {
onToggledPaused() {
// When a runner becomes Paused, the tab count can
// become stale, refetch outdated counts.
- this.$refs['runner-type-tabs'].refetch();
+ this.refetchCounts();
},
onDeleted({ message }) {
+ this.refetchCounts();
this.$root.$toast?.show(message);
},
+ refetchCounts() {
+ this.$apollo.getClient().refetchQueries({ include: [allRunnersCountQuery] });
+ },
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
@@ -152,6 +159,9 @@ export default {
isChecked,
});
},
+ onPaginationInput(value) {
+ this.search.pagination = value;
+ },
},
filteredSearchNamespace: ADMIN_FILTERED_SEARCH_NAMESPACE,
INSTANCE_TYPE,
@@ -163,7 +173,6 @@ export default {
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
>
<runner-type-tabs
- ref="runner-type-tabs"
v-model="search"
:count-scope="$options.INSTANCE_TYPE"
:count-variables="countVariables"
@@ -196,13 +205,20 @@ export default {
:filtered-svg-path="emptyStateFilteredSvgPath"
/>
<template v-else>
- <runner-bulk-delete v-if="isBulkDeleteEnabled" />
+ <runner-bulk-delete
+ v-if="isBulkDeleteEnabled"
+ :runners="runners.items"
+ @deleted="onDeleted"
+ />
<runner-list
:runners="runners.items"
:loading="runnersLoading"
:checkable="isBulkDeleteEnabled"
@checked="onChecked"
>
+ <template v-if="isBulkDeleteEnabled" #head-checkbox>
+ <runner-bulk-delete-checkbox :runners="runners.items" />
+ </template>
<template #runner-name="{ runner }">
<gl-link :href="runner.adminUrl">
<runner-name :runner="runner" />
@@ -217,11 +233,13 @@ export default {
/>
</template>
</runner-list>
- <runner-pagination
- v-model="search.pagination"
- class="gl-mt-3"
- :page-info="runners.pageInfo"
- />
</template>
+
+ <runner-pagination
+ class="gl-mt-3"
+ :disabled="runnersLoading"
+ :page-info="runners.pageInfo"
+ @input="onPaginationInput"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue
index 1eb383a1904..1cd098d6713 100644
--- a/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_summary_cell.vue
@@ -59,7 +59,11 @@ export default {
<tooltip-on-truncate class="gl-display-block gl-text-truncate" :title="description">
{{ description }}
</tooltip-on-truncate>
- <tooltip-on-truncate class="gl-display-block gl-text-truncate" :title="ipAddress">
+ <tooltip-on-truncate
+ v-if="ipAddress"
+ class="gl-display-block gl-text-truncate"
+ :title="ipAddress"
+ >
<span class="gl-md-display-none gl-lg-display-inline">{{ __('IP Address') }}</span>
<strong>{{ ipAddress }}</strong>
</tooltip-on-truncate>
diff --git a/app/assets/javascripts/runner/components/runner_assigned_item.vue b/app/assets/javascripts/runner/components/runner_assigned_item.vue
index 38bdfecb7df..2fa87bdd776 100644
--- a/app/assets/javascripts/runner/components/runner_assigned_item.vue
+++ b/app/assets/javascripts/runner/components/runner_assigned_item.vue
@@ -1,10 +1,11 @@
<script>
-import { GlAvatar, GlLink } from '@gitlab/ui';
+import { GlAvatar, GlBadge, GlLink } from '@gitlab/ui';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
export default {
components: {
GlAvatar,
+ GlBadge,
GlLink,
},
props: {
@@ -25,6 +26,16 @@ export default {
required: false,
default: null,
},
+ description: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ isOwner: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
AVATAR_SHAPE_OPTION_RECT,
};
@@ -41,7 +52,12 @@ export default {
:size="48"
/>
</gl-link>
-
- <gl-link :href="href" class="gl-font-weight-bold gl-text-gray-900!">{{ fullName }}</gl-link>
+ <div>
+ <div class="gl-mb-1">
+ <gl-link :href="href" class="gl-font-weight-bold gl-text-gray-900!">{{ fullName }}</gl-link>
+ <gl-badge v-if="isOwner" variant="info">{{ s__('Runner|Owner') }}</gl-badge>
+ </div>
+ <div v-if="description">{{ description }}</div>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete.vue b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
index 50791de0bda..703da01d9c8 100644
--- a/app/assets/javascripts/runner/components/runner_bulk_delete.vue
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete.vue
@@ -1,21 +1,31 @@
<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 { GlButton, GlModalDirective, GlModal, GlSprintf } from '@gitlab/ui';
+import { createAlert } from '~/flash';
+import { __, s__, n__, sprintf } from '~/locale';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
+import BulkRunnerDelete from '../graphql/list/bulk_runner_delete.mutation.graphql';
+import { RUNNER_TYPENAME } from '../constants';
export default {
components: {
GlButton,
+ GlModal,
GlSprintf,
},
directives: {
GlModal: GlModalDirective,
},
inject: ['localMutations'],
+ props: {
+ runners: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ },
data() {
return {
+ isDeleting: false,
checkedRunnerIds: [],
};
},
@@ -25,8 +35,13 @@ export default {
},
},
computed: {
+ currentCheckedRunnerIds() {
+ return this.runners
+ .map(({ id }) => id)
+ .filter((id) => this.checkedRunnerIds.indexOf(id) >= 0);
+ },
checkedCount() {
- return this.checkedRunnerIds.length || 0;
+ return this.currentCheckedRunnerIds.length || 0;
},
bannerMessage() {
return sprintf(
@@ -43,48 +58,103 @@ export default {
modalTitle() {
return n__('Runners|Delete %d runner', 'Runners|Delete %d runners', this.checkedCount);
},
- modalHtmlMessage() {
+ modalActionPrimary() {
+ return {
+ text: n__(
+ 'Runners|Permanently delete %d runner',
+ 'Runners|Permanently delete %d runners',
+ this.checkedCount,
+ ),
+ attributes: {
+ loading: this.isDeleting,
+ variant: 'danger',
+ },
+ };
+ },
+ modalActionCancel() {
+ return {
+ text: __('Cancel'),
+ attributes: {
+ loading: this.isDeleting,
+ },
+ };
+ },
+ modalMessage() {
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,
+ { count: this.checkedCount },
);
},
- primaryBtnText() {
+ },
+ methods: {
+ toastConfirmationMessage(deletedCount) {
return n__(
- 'Runners|Permanently delete %d runner',
- 'Runners|Permanently delete %d runners',
- this.checkedCount,
+ 'Runners|%d selected runner deleted',
+ 'Runners|%d selected runners deleted',
+ deletedCount,
);
},
- },
- 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,
- });
+ async onConfirmDelete(e) {
+ this.isDeleting = true;
+ e.preventDefault(); // don't close modal until deletion is complete
+
+ try {
+ await this.$apollo.mutate({
+ mutation: BulkRunnerDelete,
+ variables: {
+ input: {
+ ids: this.currentCheckedRunnerIds,
+ },
+ },
+ update: (cache, { data }) => {
+ const { errors, deletedIds } = data.bulkRunnerDelete;
+
+ if (errors?.length) {
+ this.onError(new Error(errors.join(' ')));
+ this.$refs.modal.hide();
+ return;
+ }
+
+ this.$emit('deleted', {
+ message: this.toastConfirmationMessage(deletedIds.length),
+ });
- if (confirmed) {
- // TODO Call $apollo.mutate with list of runner
- // ids in `this.checkedRunnerIds`.
- // See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
+ // Clean up
+
+ // Remove deleted runners from the cache
+ deletedIds.forEach((id) => {
+ const cacheId = cache.identify({ __typename: RUNNER_TYPENAME, id });
+ cache.evict({ id: cacheId });
+ });
+ cache.gc();
+
+ this.$refs.modal.hide();
+ },
+ });
+ } catch (error) {
+ this.onError(error);
+ } finally {
+ this.isDeleting = false;
}
- }),
+ },
+ onError(error) {
+ createAlert({
+ message: s__(
+ 'Runners|Something went wrong while deleting. Please refresh the page to try again.',
+ ),
+ captureError: true,
+ error,
+ });
+ },
},
+ BULK_DELETE_MODAL_ID: 'bulk-delete-modal',
};
</script>
@@ -99,13 +169,28 @@ export default {
</gl-sprintf>
</div>
<div class="gl-ml-auto">
- <gl-button data-testid="clear-btn" variant="default" @click="onClearChecked">{{
+ <gl-button variant="default" @click="onClearChecked">{{
s__('Runners|Clear selection')
}}</gl-button>
- <gl-button data-testid="delete-btn" variant="danger" @click="onClickDelete">{{
+ <gl-button v-gl-modal="$options.BULK_DELETE_MODAL_ID" variant="danger">{{
s__('Runners|Delete selected')
}}</gl-button>
</div>
</div>
+ <gl-modal
+ ref="modal"
+ size="sm"
+ :modal-id="$options.BULK_DELETE_MODAL_ID"
+ :title="modalTitle"
+ :action-primary="modalActionPrimary"
+ :action-cancel="modalActionCancel"
+ @primary="onConfirmDelete"
+ >
+ <gl-sprintf :message="modalMessage">
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
new file mode 100644
index 00000000000..dde5a5a4a05
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlFormCheckbox } from '@gitlab/ui';
+import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
+
+export default {
+ components: {
+ GlFormCheckbox,
+ },
+ inject: ['localMutations'],
+ props: {
+ runners: {
+ type: Array,
+ default: () => [],
+ required: false,
+ },
+ },
+ data() {
+ return {
+ checkedRunnerIds: [],
+ };
+ },
+ apollo: {
+ checkedRunnerIds: {
+ query: checkedRunnerIdsQuery,
+ },
+ },
+ computed: {
+ disabled() {
+ return !this.runners.length;
+ },
+ checked() {
+ return Boolean(this.runners.length) && this.runners.every(this.isChecked);
+ },
+ indeterminate() {
+ return !this.checked && this.runners.some(this.isChecked);
+ },
+ },
+ methods: {
+ isChecked({ id }) {
+ return this.checkedRunnerIds.indexOf(id) >= 0;
+ },
+ onChange($event) {
+ this.localMutations.setRunnersChecked({
+ runners: this.runners,
+ isChecked: $event,
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-checkbox
+ :indeterminate="indeterminate"
+ :checked="checked"
+ :disabled="disabled"
+ @change="onChange"
+ />
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_detail.vue b/app/assets/javascripts/runner/components/runner_detail.vue
index db67acef3db..584f77b7648 100644
--- a/app/assets/javascripts/runner/components/runner_detail.vue
+++ b/app/assets/javascripts/runner/components/runner_detail.vue
@@ -38,11 +38,10 @@ export default {
</script>
<template>
- <div class="gl-display-flex gl-pb-4">
- <dt class="gl-mr-2">{{ label }}</dt>
- <dd class="gl-mb-0">
- <!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
- <template v-if="value || $slots.value">
+ <div class="gl-display-contents">
+ <dt class="gl-mb-5 gl-mr-6 gl-max-w-26">{{ label }}</dt>
+ <dd class="gl-mb-5">
+ <template v-if="value || $scopedSlots.value">
<slot name="value">{{ value }}</slot>
</template>
<span v-else class="gl-text-gray-500">{{ emptyValue }}</span>
diff --git a/app/assets/javascripts/runner/components/runner_details.vue b/app/assets/javascripts/runner/components/runner_details.vue
index 60469d26dd5..d5222f39b81 100644
--- a/app/assets/javascripts/runner/components/runner_details.vue
+++ b/app/assets/javascripts/runner/components/runner_details.vue
@@ -51,6 +51,9 @@ export default {
}
return null;
},
+ tagList() {
+ return this.runner.tagList || [];
+ },
isGroupRunner() {
return this.runner?.runnerType === GROUP_TYPE;
},
@@ -66,14 +69,17 @@ export default {
<div>
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
<div class="gl-pt-4">
- <dl class="gl-mb-0" data-testid="runner-details-list">
+ <dl
+ class="gl-mb-0 gl-display-grid runner-details-grid-template"
+ data-testid="runner-details-list"
+ >
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
<runner-detail
:label="s__('Runners|Last contact')"
:empty-value="s__('Runners|Never contacted')"
>
- <template #value>
- <time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
+ <template v-if="runner.contactedAt" #value>
+ <time-ago :time="runner.contactedAt" />
</template>
</runner-detail>
<runner-detail :label="s__('Runners|Version')">
@@ -87,8 +93,8 @@ export default {
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
<runner-detail :label="s__('Runners|Configuration')">
- <template #value>
- <gl-intersperse v-if="configTextProtected || configTextUntagged">
+ <template v-if="configTextProtected || configTextUntagged" #value>
+ <gl-intersperse>
<span v-if="configTextProtected">{{ configTextProtected }}</span>
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
</gl-intersperse>
@@ -96,13 +102,8 @@ export default {
</runner-detail>
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
<runner-detail :label="s__('Runners|Tags')">
- <template #value>
- <runner-tags
- v-if="runner.tagList && runner.tagList.length"
- class="gl-vertical-align-middle"
- :tag-list="runner.tagList"
- size="sm"
- />
+ <template v-if="tagList.length" #value>
+ <runner-tags class="gl-vertical-align-middle" :tag-list="tagList" size="sm" />
</template>
</runner-detail>
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 bff5ec9b238..5a9ab21a457 100644
--- a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue
+++ b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue
@@ -64,19 +64,19 @@ export default {
},
methods: {
onFilter(filters) {
- // Apply new filters, from page 1
+ // Apply new filters, resetting pagination
this.$emit('input', {
...this.value,
filters,
- pagination: { page: 1 },
+ pagination: {},
});
},
onSort(sort) {
- // Apply new sort, from page 1
+ // Apply new sort, resetting pagination
this.$emit('input', {
...this.value,
sort,
- pagination: { page: 1 },
+ pagination: {},
});
},
},
diff --git a/app/assets/javascripts/runner/components/runner_jobs.vue b/app/assets/javascripts/runner/components/runner_jobs.vue
index 57afdc4b9be..9003eba3636 100644
--- a/app/assets/javascripts/runner/components/runner_jobs.vue
+++ b/app/assets/javascripts/runner/components/runner_jobs.vue
@@ -27,9 +27,7 @@ export default {
items: [],
pageInfo: {},
},
- pagination: {
- page: 1,
- },
+ pagination: {},
};
},
apollo: {
@@ -62,6 +60,11 @@ export default {
return this.$apollo.queries.jobs.loading;
},
},
+ methods: {
+ onPaginationInput(value) {
+ this.pagination = value;
+ },
+ },
I18N_NO_JOBS_FOUND,
};
</script>
@@ -74,6 +77,6 @@ export default {
<runner-jobs-table v-else-if="jobs.items.length" :jobs="jobs.items" />
<p v-else>{{ $options.I18N_NO_JOBS_FOUND }}</p>
- <runner-pagination v-model="pagination" :disabled="loading" :page-info="jobs.pageInfo" />
+ <runner-pagination :disabled="loading" :page-info="jobs.pageInfo" @input="onPaginationInput" />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index f1f99c728c5..2e406f71792 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTableLite, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
+import { GlFormCheckbox, GlTableLite, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -23,6 +23,7 @@ const defaultFields = [
export default {
components: {
+ GlFormCheckbox,
GlTableLite,
GlSkeletonLoader,
TooltipOnTruncate,
@@ -123,19 +124,11 @@ export default {
fixed
>
<template #head(checkbox)>
- <!--
- Checkbox to select all to be added here
- See https://gitlab.com/gitlab-org/gitlab/-/issues/339525/
- -->
- <span></span>
+ <slot name="head-checkbox"></slot>
</template>
<template #cell(checkbox)="{ item }">
- <input
- type="checkbox"
- :checked="isChecked(item)"
- @change="onCheckboxChange(item, $event.target.checked)"
- />
+ <gl-form-checkbox :checked="isChecked(item)" @change="onCheckboxChange(item, $event)" />
</template>
<template #head(status)="{ label }">
diff --git a/app/assets/javascripts/runner/components/runner_pagination.vue b/app/assets/javascripts/runner/components/runner_pagination.vue
index cfc21d1407b..a5bf3074dd1 100644
--- a/app/assets/javascripts/runner/components/runner_pagination.vue
+++ b/app/assets/javascripts/runner/components/runner_pagination.vue
@@ -1,18 +1,12 @@
<script>
-import { GlPagination } from '@gitlab/ui';
+import { GlKeysetPagination } from '@gitlab/ui';
export default {
components: {
- GlPagination,
+ GlKeysetPagination,
},
+ inheritAttrs: false,
props: {
- value: {
- required: false,
- type: Object,
- default: () => ({
- page: 1,
- }),
- },
pageInfo: {
required: false,
type: Object,
@@ -20,46 +14,37 @@ export default {
},
},
computed: {
- prevPage() {
- return this.pageInfo?.hasPreviousPage ? this.value.page - 1 : null;
+ paginationProps() {
+ return { ...this.pageInfo, ...this.$attrs };
},
- nextPage() {
- return this.pageInfo?.hasNextPage ? this.value.page + 1 : null;
+ isShown() {
+ const { hasPreviousPage, hasNextPage } = this.pageInfo;
+ return hasPreviousPage || hasNextPage;
},
},
methods: {
- handlePageChange(page) {
- if (page === 1) {
- // Small optimization for first page
- // If we have loaded using "first",
- // page is already cached.
- this.$emit('input', {
- page,
- });
- } else if (page > this.value.page) {
- this.$emit('input', {
- page,
- after: this.pageInfo.endCursor,
- });
- } else {
- this.$emit('input', {
- page,
- before: this.pageInfo.startCursor,
- });
- }
+ prevPage() {
+ this.$emit('input', {
+ before: this.pageInfo.startCursor,
+ });
+ },
+ nextPage() {
+ this.$emit('input', {
+ after: this.pageInfo.endCursor,
+ });
},
},
};
</script>
<template>
- <gl-pagination
- v-bind="$attrs"
- :value="value.page"
- :prev-page="prevPage"
- :next-page="nextPage"
- align="center"
- class="gl-pagination"
- @input="handlePageChange"
- />
+ <div v-if="isShown" class="gl-text-center">
+ <gl-keyset-pagination
+ v-bind="paginationProps"
+ :prev-text="s__('Pagination|Prev')"
+ :next-text="s__('Pagination|Next')"
+ @prev="prevPage"
+ @next="nextPage"
+ />
+ </div>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_projects.vue b/app/assets/javascripts/runner/components/runner_projects.vue
index c0c0c14e91e..2c1d2fc2b10 100644
--- a/app/assets/javascripts/runner/components/runner_projects.vue
+++ b/app/assets/javascripts/runner/components/runner_projects.vue
@@ -30,13 +30,12 @@ export default {
data() {
return {
projects: {
+ ownerProjectId: null,
items: [],
pageInfo: {},
count: 0,
},
- pagination: {
- page: 1,
- },
+ pagination: {},
};
},
apollo: {
@@ -48,6 +47,7 @@ export default {
update(data) {
const { runner } = data;
return {
+ ownerProjectId: runner?.ownerProject?.id,
count: runner?.projectCount || 0,
items: runner?.projects?.nodes || [],
pageInfo: runner?.projects?.pageInfo || {},
@@ -76,6 +76,14 @@ export default {
});
},
},
+ methods: {
+ isOwner(projectId) {
+ return projectId === this.projects.ownerProjectId;
+ },
+ onPaginationInput(value) {
+ this.pagination = value;
+ },
+ },
I18N_NONE,
};
</script>
@@ -98,10 +106,16 @@ export default {
:name="project.name"
:full-name="project.nameWithNamespace"
:avatar-url="project.avatarUrl"
+ :description="project.description"
+ :is-owner="isOwner(project.id)"
/>
</template>
<span v-else class="gl-text-gray-500">{{ $options.I18N_NONE }}</span>
- <runner-pagination v-model="pagination" :disabled="loading" :page-info="projects.pageInfo" />
+ <runner-pagination
+ :disabled="loading"
+ :page-info="projects.pageInfo"
+ @input="onPaginationInput"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_count.vue b/app/assets/javascripts/runner/components/stat/runner_count.vue
index af18b203f90..37c6f922f9a 100644
--- a/app/assets/javascripts/runner/components/stat/runner_count.vue
+++ b/app/assets/javascripts/runner/components/stat/runner_count.vue
@@ -1,8 +1,9 @@
<script>
import { fetchPolicies } from '~/lib/graphql';
+import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql';
+import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql';
+
import { captureException } from '../../sentry_utils';
-import allRunnersCountQuery from '../../graphql/list/all_runners_count.query.graphql';
-import groupRunnersCountQuery from '../../graphql/list/group_runners_count.query.graphql';
import { INSTANCE_TYPE, GROUP_TYPE } from '../../constants';
/**
@@ -38,7 +39,7 @@ export default {
variables: {
type: Object,
required: false,
- default: () => {},
+ default: () => ({}),
},
skip: {
type: Boolean,
diff --git a/app/assets/javascripts/runner/components/stat/runner_single_stat.vue b/app/assets/javascripts/runner/components/stat/runner_single_stat.vue
new file mode 100644
index 00000000000..ae732b052ac
--- /dev/null
+++ b/app/assets/javascripts/runner/components/stat/runner_single_stat.vue
@@ -0,0 +1,41 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { formatNumber } from '~/locale';
+import RunnerCount from './runner_count.vue';
+
+export default {
+ components: {
+ GlSingleStat,
+ RunnerCount,
+ },
+ props: {
+ scope: {
+ type: String,
+ required: true,
+ },
+ variables: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ skip: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ methods: {
+ formattedValue(value) {
+ if (typeof value === 'number') {
+ return formatNumber(value);
+ }
+ return '-';
+ },
+ },
+};
+</script>
+<template>
+ <runner-count #default="{ count }" :scope="scope" :variables="variables" :skip="skip">
+ <gl-single-stat v-bind="$attrs" :value="formattedValue(count)" />
+ </runner-count>
+</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_stats.vue b/app/assets/javascripts/runner/components/stat/runner_stats.vue
index 9e1ca9ba4ee..93e54ebe7f4 100644
--- a/app/assets/javascripts/runner/components/stat/runner_stats.vue
+++ b/app/assets/javascripts/runner/components/stat/runner_stats.vue
@@ -1,12 +1,13 @@
<script>
+import { s__ } from '~/locale';
+import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue';
import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
-import RunnerCount from './runner_count.vue';
-import RunnerStatusStat from './runner_status_stat.vue';
export default {
components: {
- RunnerCount,
- RunnerStatusStat,
+ RunnerSingleStat,
+ RunnerUpgradeStatusStats: () =>
+ import('ee_component/runner/components/stat/runner_upgrade_status_stats.vue'),
},
props: {
scope: {
@@ -16,32 +17,67 @@ export default {
variables: {
type: Object,
required: false,
- default: () => {},
+ default: () => ({}),
},
},
- methods: {
- countVariables(vars) {
- return { ...this.variables, ...vars };
+ computed: {
+ stats() {
+ return [
+ {
+ key: STATUS_ONLINE,
+ props: {
+ skip: this.statusCountSkip(STATUS_ONLINE),
+ variables: { ...this.variables, status: STATUS_ONLINE },
+ variant: 'success',
+ title: s__('Runners|Online runners'),
+ metaText: s__('Runners|online'),
+ },
+ },
+ {
+ key: STATUS_OFFLINE,
+ props: {
+ skip: this.statusCountSkip(STATUS_OFFLINE),
+ variables: { ...this.variables, status: STATUS_OFFLINE },
+ variant: 'muted',
+ title: s__('Runners|Offline runners'),
+ metaText: s__('Runners|offline'),
+ },
+ },
+ {
+ key: STATUS_STALE,
+ props: {
+ skip: this.statusCountSkip(STATUS_STALE),
+ variables: { ...this.variables, status: STATUS_STALE },
+ variant: 'warning',
+ title: s__('Runners|Stale runners'),
+ metaText: s__('Runners|stale'),
+ },
+ },
+ ];
},
+ },
+ methods: {
statusCountSkip(status) {
// Show an empty result when we already filter by another status
return this.variables.status && this.variables.status !== status;
},
},
- STATUS_LIST: [STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE],
};
</script>
<template>
- <div class="gl-display-flex gl-py-6">
- <runner-count
- v-for="status in $options.STATUS_LIST"
- #default="{ count }"
- :key="status"
+ <div class="gl-display-flex gl-flex-wrap gl-py-6">
+ <runner-single-stat
+ v-for="stat in stats"
+ :key="stat.key"
+ :scope="scope"
+ v-bind="stat.props"
+ class="gl-px-5"
+ />
+
+ <runner-upgrade-status-stats
+ class="gl-display-contents"
:scope="scope"
- :variables="countVariables({ status })"
- :skip="statusCountSkip(status)"
- >
- <runner-status-stat class="gl-px-5" :status="status" :value="count" />
- </runner-count>
+ :variables="variables"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
deleted file mode 100644
index b77bbe15541..00000000000
--- a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
-import { s__, formatNumber } from '~/locale';
-import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
-
-export default {
- components: {
- GlSingleStat,
- },
- props: {
- value: {
- type: Number,
- required: false,
- default: null,
- },
- status: {
- type: String,
- required: true,
- },
- },
- computed: {
- formattedValue() {
- if (typeof this.value === 'number') {
- return formatNumber(this.value);
- }
- return '-';
- },
- stat() {
- switch (this.status) {
- case STATUS_ONLINE:
- return {
- variant: 'success',
- title: s__('Runners|Online runners'),
- metaText: s__('Runners|online'),
- };
- case STATUS_OFFLINE:
- return {
- variant: 'muted',
- title: s__('Runners|Offline runners'),
- metaText: s__('Runners|offline'),
- };
- case STATUS_STALE:
- return {
- variant: 'warning',
- title: s__('Runners|Stale runners'),
- metaText: s__('Runners|stale'),
- };
- default:
- return {
- title: s__('Runners|Runners'),
- };
- }
- },
- },
-};
-</script>
-<template>
- <gl-single-stat
- v-if="stat"
- :value="formattedValue"
- :variant="stat.variant"
- :title="stat.title"
- :meta-text="stat.metaText"
- />
-</template>
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index 64541729701..ed1afcbf691 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -1,5 +1,7 @@
import { __, s__ } from '~/locale';
+export const RUNNER_TYPENAME = 'CiRunner'; // __typename
+
export const RUNNER_PAGE_SIZE = 20;
export const RUNNER_JOB_COUNT_LIMIT = 1000;
@@ -102,7 +104,6 @@ export const PARAM_KEY_TAG = 'tag';
export const PARAM_KEY_SEARCH = 'search';
export const PARAM_KEY_SORT = 'sort';
-export const PARAM_KEY_PAGE = 'page';
export const PARAM_KEY_AFTER = 'after';
export const PARAM_KEY_BEFORE = 'before';
diff --git a/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
index f900a0450e5..29abddf84f5 100644
--- a/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/edit/runner_fields_shared.fragment.graphql
@@ -1,5 +1,4 @@
fragment RunnerFieldsShared on CiRunner {
- __typename
id
shortSha
runnerType
diff --git a/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql b/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql
index 6bb896dda16..1160596aff3 100644
--- a/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/all_runners.query.graphql
@@ -1,5 +1,4 @@
-#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql"
-#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+#import "~/runner/graphql/list/all_runners_connection.fragment.graphql"
query getAllRunners(
$before: String
@@ -25,14 +24,6 @@ query getAllRunners(
search: $search
sort: $sort
) {
- nodes {
- ...ListItem
- adminUrl
- editAdminUrl
- }
- pageInfo {
- __typename
- ...PageInfo
- }
+ ...AllRunnersConnection
}
}
diff --git a/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql b/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql
new file mode 100644
index 00000000000..4440b8e98da
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/all_runners_connection.fragment.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+
+fragment AllRunnersConnection on CiRunnerConnection {
+ nodes {
+ ...ListItem
+ adminUrl
+ editAdminUrl
+ }
+ pageInfo {
+ ...PageInfo
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/list/bulk_runner_delete.mutation.graphql b/app/assets/javascripts/runner/graphql/list/bulk_runner_delete.mutation.graphql
new file mode 100644
index 00000000000..b73c016b1de
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/bulk_runner_delete.mutation.graphql
@@ -0,0 +1,6 @@
+mutation bulkRunnerDelete($input: BulkRunnerDeleteInput!) {
+ bulkRunnerDelete(input: $input) {
+ deletedIds
+ errors
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/list/group_runner_connection.fragment.graphql b/app/assets/javascripts/runner/graphql/list/group_runner_connection.fragment.graphql
new file mode 100644
index 00000000000..baef16a4b41
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/list/group_runner_connection.fragment.graphql
@@ -0,0 +1,16 @@
+#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql"
+#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+
+fragment GroupRunnerConnection on CiRunnerConnection {
+ edges {
+ webUrl
+ editUrl
+ node {
+ ...ListItem
+ projectCount # Used to determine why some project runners can't be deleted
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+}
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 8755636a7ad..4c519b9b867 100644
--- a/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/list/group_runners.query.graphql
@@ -1,5 +1,4 @@
-#import "ee_else_ce/runner/graphql/list/list_item.fragment.graphql"
-#import "~/graphql_shared/fragments/page_info.fragment.graphql"
+#import "~/runner/graphql/list/group_runner_connection.fragment.graphql"
query getGroupRunners(
$groupFullPath: ID!
@@ -27,18 +26,7 @@ query getGroupRunners(
search: $search
sort: $sort
) {
- edges {
- webUrl
- editUrl
- node {
- ...ListItem
- projectCount # Used to determine why some project runners can't be deleted
- }
- }
- pageInfo {
- __typename
- ...PageInfo
- }
+ ...GroupRunnerConnection
}
}
}
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
index cf925359ffb..ce23bddb898 100644
--- a/app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/list/list_item_shared.fragment.graphql
@@ -1,11 +1,9 @@
fragment ListItemShared on CiRunner {
- __typename
id
description
runnerType
shortSha
version
- revision
ipAddress
active
locked
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
index e87bc72c86a..154af261bba 100644
--- a/app/assets/javascripts/runner/graphql/list/local_state.js
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -1,4 +1,5 @@
import { makeVar } from '@apollo/client/core';
+import { RUNNER_TYPENAME } from '../../constants';
import typeDefs from './typedefs.graphql';
/**
@@ -33,10 +34,16 @@ export const createLocalState = () => {
typePolicies: {
Query: {
fields: {
- checkedRunnerIds() {
+ checkedRunnerIds(_, { canRead, toReference }) {
return Object.entries(checkedRunnerIdsVar())
+ .filter(([id]) => {
+ // Some runners may be deleted by the user separately.
+ // Skip dangling references, those not in the cache.
+ // See: https://www.apollographql.com/docs/react/caching/garbage-collection/#dangling-references
+ return canRead(toReference({ __typename: RUNNER_TYPENAME, id }));
+ })
.filter(([, isChecked]) => isChecked)
- .map(([key]) => key);
+ .map(([id]) => id);
},
},
},
@@ -50,6 +57,13 @@ export const createLocalState = () => {
[runner.id]: isChecked,
});
},
+ setRunnersChecked({ runners, isChecked }) {
+ const newVal = runners.reduce(
+ (acc, { id }) => ({ ...acc, [id]: isChecked }),
+ checkedRunnerIdsVar(),
+ );
+ checkedRunnerIdsVar(newVal);
+ },
clearChecked() {
checkedRunnerIdsVar({});
},
diff --git a/app/assets/javascripts/runner/graphql/show/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/show/runner_details_shared.fragment.graphql
index b79ad4d9280..499c0156770 100644
--- a/app/assets/javascripts/runner/graphql/show/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_details_shared.fragment.graphql
@@ -1,5 +1,4 @@
fragment RunnerDetailsShared on CiRunner {
- __typename
id
shortSha
runnerType
diff --git a/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
index cb27de7c200..acc4a641565 100644
--- a/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
+++ b/app/assets/javascripts/runner/graphql/show/runner_projects.query.graphql
@@ -9,11 +9,15 @@ query getRunnerProjects(
) {
runner(id: $id) {
id
+ ownerProject {
+ id
+ }
projectCount
projects(first: $first, last: $last, before: $before, after: $after) {
nodes {
id
avatarUrl
+ description
name
nameWithNamespace
webUrl
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 e8446dbe345..a82411a2120 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -3,6 +3,14 @@ import { GlLink } from '@gitlab/ui';
import { createAlert } from '~/flash';
import { updateHistory } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
+import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
+import {
+ fromUrlQueryToSearch,
+ fromSearchToUrl,
+ fromSearchToVariables,
+ isSearchFiltered,
+} from 'ee_else_ce/runner/runner_search_utils';
+import groupRunnersQuery from 'ee_else_ce/runner/graphql/list/group_runners.query.graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
@@ -22,13 +30,6 @@ import {
PROJECT_TYPE,
I18N_FETCH_ERROR,
} from '../constants';
-import groupRunnersQuery from '../graphql/list/group_runners.query.graphql';
-import {
- fromUrlQueryToSearch,
- fromSearchToUrl,
- fromSearchToVariables,
- isSearchFiltered,
-} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
export default {
@@ -123,7 +124,7 @@ export default {
return !this.runnersLoading && !this.runners.items.length;
},
searchTokens() {
- return [pausedTokenConfig, statusTokenConfig];
+ return [pausedTokenConfig, statusTokenConfig, upgradeStatusTokenConfig];
},
filteredSearchNamespace() {
return `${GROUP_FILTERED_SEARCH_NAMESPACE}/${this.groupFullPath}`;
@@ -166,6 +167,9 @@ export default {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
+ onPaginationInput(value) {
+ this.search.pagination = value;
+ },
},
TABS_RUNNER_TYPES: [GROUP_TYPE, PROJECT_TYPE],
GROUP_TYPE,
@@ -225,11 +229,13 @@ export default {
/>
</template>
</runner-list>
- <runner-pagination
- v-model="search.pagination"
- class="gl-mt-3"
- :page-info="runners.pageInfo"
- />
</template>
+
+ <runner-pagination
+ class="gl-mt-3"
+ :disabled="runnersLoading"
+ :page-info="runners.pageInfo"
+ @input="onPaginationInput"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js
index e01878f355a..dc582ccbac1 100644
--- a/app/assets/javascripts/runner/runner_search_utils.js
+++ b/app/assets/javascripts/runner/runner_search_utils.js
@@ -1,3 +1,4 @@
+import { isEmpty } from 'lodash';
import { queryToObject, setUrlParams } from '~/lib/utils/url_utility';
import {
filterToQueryObject,
@@ -13,7 +14,6 @@ import {
PARAM_KEY_TAG,
PARAM_KEY_SEARCH,
PARAM_KEY_SORT,
- PARAM_KEY_PAGE,
PARAM_KEY_AFTER,
PARAM_KEY_BEFORE,
DEFAULT_SORT,
@@ -41,7 +41,7 @@ import { getPaginationVariables } from './utils';
* sort: 'CREATED_DESC',
*
* // Pagination information
- * pagination: { page: 1 },
+ * pagination: { "after": "..." },
* };
* ```
*
@@ -66,25 +66,16 @@ export const searchValidator = ({ runnerType, filters, sort }) => {
};
const getPaginationFromParams = (params) => {
- const page = parseInt(params[PARAM_KEY_PAGE], 10);
- const after = params[PARAM_KEY_AFTER];
- const before = params[PARAM_KEY_BEFORE];
-
- if (page && (before || after)) {
- return {
- page,
- before,
- after,
- };
- }
return {
- page: 1,
+ after: params[PARAM_KEY_AFTER],
+ before: params[PARAM_KEY_BEFORE],
};
};
// Outdated URL parameters
const STATUS_ACTIVE = 'ACTIVE';
const STATUS_PAUSED = 'PAUSED';
+const PARAM_KEY_PAGE = 'page';
/**
* Replaces params into a URL
@@ -97,6 +88,21 @@ const updateUrlParams = (url, params = {}) => {
return setUrlParams(params, url, false, true, true);
};
+const outdatedStatusParams = (status) => {
+ if (status === STATUS_ACTIVE) {
+ return {
+ [PARAM_KEY_PAUSED]: ['false'],
+ [PARAM_KEY_STATUS]: [], // Important! clear PARAM_KEY_STATUS to avoid a redirection loop!
+ };
+ } else if (status === STATUS_PAUSED) {
+ return {
+ [PARAM_KEY_PAUSED]: ['true'],
+ [PARAM_KEY_STATUS]: [], // Important! clear PARAM_KEY_STATUS to avoid a redirection loop!
+ };
+ }
+ return {};
+};
+
/**
* Returns an updated URL for old (or deprecated) admin runner URLs.
*
@@ -108,25 +114,22 @@ const updateUrlParams = (url, params = {}) => {
export const updateOutdatedUrl = (url = window.location.href) => {
const urlObj = new URL(url);
const query = urlObj.search;
-
const params = queryToObject(query, { gatherArrays: true });
- const status = params[PARAM_KEY_STATUS]?.[0] || null;
-
- switch (status) {
- case STATUS_ACTIVE:
- return updateUrlParams(url, {
- [PARAM_KEY_PAUSED]: ['false'],
- [PARAM_KEY_STATUS]: [], // Important! clear PARAM_KEY_STATUS to avoid a redirection loop!
- });
- case STATUS_PAUSED:
- return updateUrlParams(url, {
- [PARAM_KEY_PAUSED]: ['true'],
- [PARAM_KEY_STATUS]: [], // Important! clear PARAM_KEY_STATUS to avoid a redirection loop!
- });
- default:
- return null;
+ // Remove `page` completely, not needed for keyset pagination
+ const pageParams = PARAM_KEY_PAGE in params ? { [PARAM_KEY_PAGE]: null } : {};
+
+ const status = params[PARAM_KEY_STATUS]?.[0];
+ const redirectParams = {
+ // Replace paused status (active, paused) with a paused flag
+ ...outdatedStatusParams(status),
+ ...pageParams,
+ };
+
+ if (!isEmpty(redirectParams)) {
+ return updateUrlParams(url, redirectParams);
}
+ return null;
};
/**
@@ -182,13 +185,11 @@ export const fromSearchToUrl = (
}
const isDefaultSort = sort !== DEFAULT_SORT;
- const isFirstPage = pagination?.page === 1;
const otherParams = {
// Sorting & Pagination
[PARAM_KEY_SORT]: isDefaultSort ? sort : null,
- [PARAM_KEY_PAGE]: isFirstPage ? null : pagination.page,
- [PARAM_KEY_BEFORE]: isFirstPage ? null : pagination.before,
- [PARAM_KEY_AFTER]: isFirstPage ? null : pagination.after,
+ [PARAM_KEY_BEFORE]: pagination?.before || null,
+ [PARAM_KEY_AFTER]: pagination?.after || null,
};
return setUrlParams({ ...filterParams, ...otherParams }, url, false, true, true);
@@ -247,6 +248,6 @@ export const fromSearchToVariables = ({
*/
export const isSearchFiltered = ({ runnerType = null, filters = [], pagination = {} } = {}) => {
return Boolean(
- runnerType !== null || filters?.length !== 0 || (pagination && pagination?.page !== 1),
+ runnerType !== null || filters?.length !== 0 || pagination?.before || pagination?.after,
);
};