vault/ui/docs/client-pagination.md
Chelsea Shaw 392b907989
KV V2 remove old kv v2 (#22691)
* Remove component: diff version selector

* delete SecretVersionMenu

* remove secret logic from GetCredentialsCard

* remove DiffVersionSelector hbs file and references

* delete more css for diff version view

* remove diff route

* fix credential card selector

* ui: refactor SecretFormShow (#22723)

* refactor secret form show

* fix selector typo

* remove version route (#22738)

* Remove old KV2 delete things (#23015)

* remove kv2 old delete things

* comment

* Remove old metadata (#22747)

* wip to remove metadata

* review comments

* UI/remove kv2 secret create or update (#23039)

* remove is v2 param

* permissions clean up

* remove version things

* remove excess from form show

* clean up

* created time was never a thing for cubbyhole, confirmed on api

* update tune test

* fix control group tests:

* Remove kv v2 models (#23087)

* remove is v2 param

* permissions clean up

* remove version things

* remove excess from form show

* clean up

* created time was never a thing for cubbyhole, confirmed on api

* update tune test

* fix control group tests:

* remove models

* Update ui/app/models/secret-engine.js

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

* blah prettier

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

* UI/config update (#23111)

* sweep through clean up

* remove component

* remove unused selectors

* remove unncessary

---------

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
Co-authored-by: clairebontempo@gmail.com <clairebontempo@gmail.com>
Co-authored-by: Angel Garbarino <Monkeychip@users.noreply.github.com>
Co-authored-by: Angel Garbarino <angel@hashicorp.com>
2023-09-19 09:49:04 -06:00

4.0 KiB

Client-side pagination

Our custom extended store service allows us to paginate LIST responses while maintaining good performance, particularly when the LIST response includes tens of thousands of keys in the data response. It does this by caching the entire response, and then filtering the full response into the datastore for the client.

Using pagination

Rather than use store.query, use store.lazyPaginatedQuery. It generally uses the same inputs, but accepts additional keys in the query object size, page, responsePath, pageFilter

Before

export default class ExampleRoute extends Route {
  @service store;

  model(params) {
    const { secret } = params;
    const { backend } = this.paramsFor('vault.cluster.secrets.backend');
    return this.store.query('pki/role', { backend, id })
  }

After

export default class ExampleRoute extends Route {
  @service store;

  model(params) {
    const { page, pageFilter, secret } = params;
    const { backend } = this.paramsFor('vault.cluster.secrets.backend');
    return this.store.lazyPaginatedQuery('secret', {
      backend,
      id: secret,
      size,
      page,
      responsePath,
      pageFilter
    })
  }

The size param defaults to the default page size set in the app config. responsePath and page are required, and typically responsePath is going to be data.keys since that is where the LIST responses typically return their array data.

Serializing

In order to interrupt the regular serialization when using lazyPaginatedData, define extractLazyPaginatedData on the modelType's serializer. This will be called with the raw response before being cached on the store.

Gotchas

The data is cached from whenever the original API call is made, which means that if a user views a list and then creates or deletes an item, viewing the list page again will show outdated information unless the cache for the item is cleared first. For this reason, it is best practice to clear the dataset with store.clearDataset(modelName) after successfully deleting or creating an item.

How it works

When using the lazyPaginatedQuery method, the full response is cached in a tracked Map within the service. store.lazyCaches is actually a Map of Maps, keyed first on the normalized modelType and then on a stringified version of the base query (all keys except ones related to pagination). So, at the top level store.lazyCaches looks like this:

lazyCaches = new Map({
  "secret": <Map>,
  "kmip": <Map>,
  "namespace": <Map>,
})

Within each top-level modelType, we need to separate cached responses based on the details of the query. Typically (but not always) this includes the backend name. In list items that can be nested (see KV V2 secrets or namespaces for example) id is also provided, so that the keys nested under the given ID is returned. The store.lazyCaches may look something like the following after a user navigates to a couple different KV v2 lists, and clicks into the app/ item:

lazyCaches = new Map({
  "secret": {
    "{ backend: 'secret', id: '' }: <CachedData>,
    "{ backend: 'secret', id: 'app/' }: <CachedData>,
    "{ backend: 'kv2', id: '' }: <CachedData>,
  },
  ...
})

The cached data at the given key is an object with response and dataset keys. The response is the full response from the original API call, with the responsePath nulled out (it is repopulated before "sending" the data back to the store). dataset is the full, original value at responsePath, usually an array of strings. An example of what the data might look like:

lazyCaches = new Map({
  "secret": {
    "{ backend: 'secret', id: 'app/' }: {
      dataset: ['some', 'nested', 'secrets'],
      response: {
        request_id: 'foobar',
        data: {},
        ...
      }
    },
  },
  ...
})