diff --git a/changelog/13195.txt b/changelog/13195.txt new file mode 100644 index 000000000..0f9ce70c0 --- /dev/null +++ b/changelog/13195.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Added client side paging for namespace list view +``` \ No newline at end of file diff --git a/ui/app/adapters/namespace.js b/ui/app/adapters/namespace.js index d403841cd..2c9d8a97e 100644 --- a/ui/app/adapters/namespace.js +++ b/ui/app/adapters/namespace.js @@ -31,4 +31,7 @@ export default ApplicationAdapter.extend({ } return this._super(...arguments); }, + query() { + return this.ajax(`/${this.urlPrefix()}/namespaces?list=true`); + }, }); diff --git a/ui/app/controllers/vault/cluster/access/namespaces/index.js b/ui/app/controllers/vault/cluster/access/namespaces/index.js index 87487c1ff..c2b6041da 100644 --- a/ui/app/controllers/vault/cluster/access/namespaces/index.js +++ b/ui/app/controllers/vault/cluster/access/namespaces/index.js @@ -9,6 +9,7 @@ export default Controller.extend({ refreshNamespaceList() { // fetch new namespaces for the namespace picker this.namespaceService.findNamespacesForUser.perform(); + this.send('reload'); }, }, }); diff --git a/ui/app/routes/vault/cluster/access/namespaces/index.js b/ui/app/routes/vault/cluster/access/namespaces/index.js index c756e607d..f598997fa 100644 --- a/ui/app/routes/vault/cluster/access/namespaces/index.js +++ b/ui/app/routes/vault/cluster/access/namespaces/index.js @@ -3,6 +3,11 @@ import Route from '@ember/routing/route'; import UnloadModel from 'vault/mixins/unload-model-route'; export default Route.extend(UnloadModel, { + queryParams: { + page: { + refreshModel: true, + }, + }, version: service(), beforeModel() { this.store.unloadAll('namespace'); @@ -10,14 +15,62 @@ export default Route.extend(UnloadModel, { return this._super(...arguments); }); }, - model() { - return this.version.hasNamespaces - ? this.store.findAll('namespace').catch(e => { - if (e.httpStatus === 404) { - return []; - } - throw e; + + model(params) { + if (this.version.hasNamespaces) { + return this.store + .lazyPaginatedQuery('namespace', { + responsePath: 'data.keys', + page: Number(params?.page) || 1, }) - : null; + .then(model => { + return model; + }) + .catch(err => { + if (err.httpStatus === 404) { + return []; + } else { + throw err; + } + }); + } + return null; + }, + + setupController(controller, model) { + const has404 = this.has404; + controller.setProperties({ + model: model, + has404, + hasModel: true, + }); + if (!has404) { + controller.setProperties({ + page: Number(model?.meta?.currentPage) || 1, + }); + } + }, + actions: { + error(error, transition) { + /* eslint-disable-next-line ember/no-controller-access-in-routes */ + const hasModel = this.controllerFor(this.routeName).get('hasModel'); + if (hasModel && error.httpStatus === 404) { + this.set('has404', true); + transition.abort(); + } else { + return true; + } + }, + willTransition(transition) { + window.scrollTo(0, 0); + if (!transition || transition.targetName !== this.routeName) { + this.store.clearAllDatasets(); + } + return true; + }, + reload() { + this.store.clearAllDatasets(); + this.refresh(); + }, }, }); diff --git a/ui/app/templates/vault/cluster/access/namespaces/index.hbs b/ui/app/templates/vault/cluster/access/namespaces/index.hbs index 4a1bf1b9f..f8c0e0c60 100644 --- a/ui/app/templates/vault/cluster/access/namespaces/index.hbs +++ b/ui/app/templates/vault/cluster/access/namespaces/index.hbs @@ -18,7 +18,7 @@ - + {{#if list.empty}} diff --git a/ui/mirage/config.js b/ui/mirage/config.js index b3a2b0df7..efde1e3ad 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -272,5 +272,32 @@ export default function() { }; }); + this.get('sys/namespaces', function() { + return { + data: { + keys: [ + 'ns1/', + 'ns2/', + 'ns3/', + 'ns4/', + 'ns5/', + 'ns6/', + 'ns7/', + 'ns8/', + 'ns9/', + 'ns10/', + 'ns11/', + 'ns12/', + 'ns13/', + 'ns14/', + 'ns15/', + 'ns16/', + 'ns17/', + 'ns18/', + ], + }, + }; + }); + this.passthrough(); } diff --git a/ui/tests/acceptance/access/namespaces/index-test.js b/ui/tests/acceptance/access/namespaces/index-test.js new file mode 100644 index 000000000..908f3d4cf --- /dev/null +++ b/ui/tests/acceptance/access/namespaces/index-test.js @@ -0,0 +1,40 @@ +import { currentRouteName } from '@ember/test-helpers'; +import { module, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import page from 'vault/tests/pages/access/namespaces/index'; +import authPage from 'vault/tests/pages/auth'; +import logout from 'vault/tests/pages/logout'; + +module('Acceptance | Enterprise | /access/namespaces', function(hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + hooks.beforeEach(function() { + return authPage.login(); + }); + + hooks.afterEach(function() { + return logout.visit(); + }); + + test('it navigates to namespaces page', async function(assert) { + assert.expect(1); + await page.visit(); + assert.equal( + currentRouteName(), + 'vault.cluster.access.namespaces.index', + 'navigates to the correct route' + ); + }); + + test('it should render correct number of namespaces', async function(assert) { + assert.expect(3); + await page.visit(); + const store = this.owner.lookup('service:store'); + // Default page size is 15 + assert.equal(store.peekAll('namespace').length, 15, 'Store has 15 namespaces records'); + assert.dom('.list-item-row').exists({ count: 15 }); + assert.dom('[data-test-list-view-pagination]').exists(); + }); +}); diff --git a/ui/tests/pages/access/namespaces/index.js b/ui/tests/pages/access/namespaces/index.js new file mode 100644 index 000000000..8eb174bc4 --- /dev/null +++ b/ui/tests/pages/access/namespaces/index.js @@ -0,0 +1,5 @@ +import { create, visitable } from 'ember-cli-page-object'; + +export default create({ + visit: visitable('/vault/access/namespaces'), +});