Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add server-side pagination to cluster explorer lists #11672

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions shell/components/AsyncButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ export default defineComponent({
return this.disabled || this.phase === ASYNC_BUTTON_STATES.WAITING;
},

isManualRefresh() {
return this.mode === 'manual-refresh';
},

tooltip(): { content: string, hideOnTargetClick: boolean} | null {
if ( this.labelAs === TOOLTIP ) {
return {
Expand Down Expand Up @@ -283,11 +287,14 @@ export default defineComponent({
:data-testid="componentTestid + '-async-button'"
@click="clicked"
>
<span v-if="mode === 'manual-refresh'">{{ t('action.refresh') }}</span>
<span
v-if="isManualRefresh"
:class="{'mr-10': displayIcon && size !== 'sm', 'mr-5': displayIcon && size === 'sm'}"
>{{ t('action.refresh') }}</span>
<i
v-if="displayIcon"
v-clean-tooltip="tooltip"
:class="{icon: true, 'icon-lg': true, [displayIcon]: true}"
:class="{icon: true, 'icon-lg': true, [displayIcon]: true, 'mr-0': isManualRefresh}"
/>
<span
v-if="labelAs === 'text' && displayLabel"
Expand Down
127 changes: 127 additions & 0 deletions shell/components/PaginatedResourceTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<script lang="ts">
import { defineComponent } from 'vue';
import ResourceFetch from '@shell/mixins/resource-fetch';
import ResourceTable from '@shell/components/ResourceTable.vue';
import { StorePaginationResult } from 'types/store/pagination.types';

export type FetchSecondaryResourcesOpts = { }
export type FetchSecondaryResources = (opts: FetchSecondaryResourcesOpts) => Promise<any>

export type FetchPageSecondaryResourcesOpts = { canPaginate: boolean, force: boolean, page: any[], pagResult: StorePaginationResult }
export type FetchPageSecondaryResources = (opts: FetchPageSecondaryResourcesOpts) => Promise<any>

export const PAGINATED_RESOURCE_TABLE_NAME = 'PaginatedResourceTable';

/**
* This is meant to enable ResourceList like capabilities outside of List pages / components
*
* Specifically
* - Resource Fetch features, including server-side pagination
* - Some plumbing
*
* This avoids polluting the owning component with mixins
*
*/
export default defineComponent({
name: PAGINATED_RESOURCE_TABLE_NAME,

components: { ResourceTable },

mixins: [ResourceFetch],

props: {
schema: {
type: Object,
required: true,
},

headers: {
type: Array,
default: null,
},

paginationHeaders: {
type: Array,
default: null,
},

namespaced: {
type: Boolean,
default: null, // Automatic from schema
},

/**
* Information may be required from resources other than the primary one shown per row
*
* This will fetch them ALL and will be run in a non-server-side pagination world
*/
fetchSecondaryResources: {
type: Function,
default: null,
},

/**
* Information may be required from resources other than the primary one shown per row
*
* This will fetch only those relevent to the current page using server-side pagination based filters
*
* called from shell/mixins/resource-fetch-api-pagination.js
*/
fetchPageSecondaryResources: {
type: Function,
default: null,
}
},

data() {
return { resource: this.schema.id };
},

async fetch() {
await this.$fetchType(this.resource, [], this.inStore);

if (!this.canPaginate && this.fetchSecondaryResources) {
await this.fetchSecondaryResources({ });
}
},

computed: {
safeHeaders() {
const customHeaders = this.canPaginate ? this.paginationHeaders : this.headers;

return customHeaders || this.$store.getters['type-map/headersFor'](this.schema, this.canPaginate);
}
}
});

</script>

<template>
<div>
<ResourceTable
v-bind="$attrs"
:schema="schema"
:rows="rows"
:alt-loading="canPaginate"
:loading="loading"

:headers="safeHeaders"
:namespaced="namespaced"

:external-pagination-enabled="canPaginate"
:external-pagination-result="paginationResult"
@pagination-changed="paginationChanged"
>
<!-- Pass down templates provided by the caller -->
<template
v-for="(_, slot) of $slots"
v-slot:[slot]="scope"
>
<slot
:name="slot"
v-bind="scope"
/>
</template>
</ResourceTable>
</div>
</template>
19 changes: 16 additions & 3 deletions shell/components/ResourceList/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ResourceListComponentName } from './resource-list.config';
import { PanelLocation, ExtensionPoint } from '@shell/core/types';
import ExtensionPanel from '@shell/components/ExtensionPanel';
import { sameContents } from '@shell/utils/array';
import { PAGINATED_RESOURCE_TABLE_NAME } from 'components/PaginatedResourceTable.vue';

export default {
name: ResourceListComponentName,
Expand Down Expand Up @@ -67,6 +68,11 @@ export default {
this.loadResources = loadResources || [resource];
this.loadIndeterminate = loadIndeterminate || false;
}

// If the custom component contains the paginated resource table it'll control the fetching
if (component?.components?.[PAGINATED_RESOURCE_TABLE_NAME]) {
this.componentWillFetch = true;
}
}

if ( !this.componentWillFetch ) {
Expand All @@ -90,13 +96,13 @@ export default {

const hasListComponent = getters['type-map/hasCustomList'](resource);

const inStore = getters['currentStore'](resource);
const schema = getters[`${ inStore }/schemaFor`](resource);
const derpinStore = getters['currentStore'](resource); // TODO: RC fix in parent commit (dupe inStore props)
const schema = getters[`${ derpinStore }/schemaFor`](resource);

const showMasthead = getters[`type-map/optionsFor`](resource).showListMasthead;

return {
inStore,
// inStore,
schema,
hasListComponent,
showMasthead: showMasthead === undefined ? true : showMasthead,
Expand Down Expand Up @@ -266,6 +272,13 @@ export default {
v-bind="$data"
/>
</div>
<!-- TODO: RC throw exception in findX that are no longer supported.. or will all be supported with tweak? should they use findPage? -->
<!-- this.uiServices = await this.$store.dispatch('cluster/findMatching', {
type: SERVICE,
selector: 'app=longhorn-ui'
}); -->
<!-- // TODO: RC BUG. Switching between lists can show the 'no rows to show' message before populating with existing rows. only happens for below and not custom? -->
<!-- ||{{ hasListComponent }}||{{ canPaginate }}|| TODO: track if canPaginate is first brielfy false. block on it being populated? -->
<ResourceTable
v-else
:schema="schema"
Expand Down
24 changes: 14 additions & 10 deletions shell/components/SortableTable/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import { getParent } from '@shell/utils/dom';
import { FORMATTERS } from '@shell/components/SortableTable/sortable-config';
import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
import devConsole from 'utils/dev-console';

// Uncomment for table performance debugging
// import tableDebug from './debug';
Expand Down Expand Up @@ -361,7 +362,13 @@ export default {
externalPaginationResult: {
type: Object,
default: null
},

manualRefreshButtonSize: {
type: String,
default: ''
}

},

data() {
Expand Down Expand Up @@ -490,6 +497,7 @@ export default {

loading: {
handler(neu, old) {
devConsole.warn('SS', 'watch', 'loading', neu, old);
// Always ensure the Refresh button phase aligns with loading state (to ensure external phase changes which can then reset the internal phase changed by click)
this.refreshButtonPhase = neu ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;

Expand All @@ -498,7 +506,7 @@ export default {
if (neu) {
this._altLoadingDelayTimer = setTimeout(() => {
this.isLoading = true;
}, 200); // this should be higher than the targetted quick response
}, 200); // this should be higher than the targeted quick response
} else {
clearTimeout(this._altLoadingDelayTimer);
this.isLoading = false;
Expand Down Expand Up @@ -571,9 +579,10 @@ export default {
showHeaderRow() {
return this.search ||
this.tableActions ||
this.$slots['header-left']?.() ||
this.$slots['header-middle']?.() ||
this.$slots['header-right']?.();
this.$slots['header-left'] ||
this.$slots['header-middle'] ||
this.$slots['header-right'] ||
this.isTooManyItemsToAutoUpdate;
},

columns() {
Expand Down Expand Up @@ -1140,8 +1149,8 @@ export default {
<slot name="header-right" />
<AsyncButton
v-if="isTooManyItemsToAutoUpdate"
class="manual-refresh"
mode="manual-refresh"
:size="manualRefreshButtonSize"
:current-phase="refreshButtonPhase"
@click="debouncedRefreshTableData"
/>
Expand Down Expand Up @@ -1399,7 +1408,6 @@ export default {
:col="col.col"
v-bind="col.col.formatterOpts"
:row-key="row.key"
:get-custom-detail-link="getCustomDetailLink"
/>
<component
:is="col.component"
Expand Down Expand Up @@ -1568,10 +1576,6 @@ export default {
opacity: 0.5;
pointer-events: none;
}

.manual-refresh {
height: 40px;
}
.advanced-filter-group {
position: relative;
margin-left: 10px;
Expand Down
6 changes: 3 additions & 3 deletions shell/components/form/ResourceLabeledSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface ResourceLabeledSelectPaginateSettings extends SharedSettings {
*/
overrideRequest?: LabelSelectPaginateFn,
/**
* Override the default settings used in the convience function to fetch a page of results
* Override the default settings used in the convenience function to fetch a page of results
*/
requestSettings?: PaginateTypeOverridesFn,
}
Expand All @@ -52,14 +52,14 @@ export enum RESOURCE_LABEL_SELECT_MODE {
}

/**
* Convience wrapper around the LabelSelect component to support pagination
* Convenience wrapper around the LabelSelect component to support pagination
*
* Handles
*
* 1) Conditionally enabling the pagination feature given system settings
* 2) Helper function to fetch the pagination result
*
* A number of ways can be provided to override the convienences (see props)
* A number of ways can be provided to override the conveniences (see props)
*/
export default defineComponent({
name: 'ResourceLabeledSelect',
Expand Down
Loading
Loading