mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 04:16:31 +02:00
UI: Ember-data upgrade 5.3.2 prep: use custom service instead of extending ember-data store (#28695)
* rename store to pagination, remove store extension * initial update of service test * remove superfluous helper * replace store with pagination service in main app * update kmip engine syntax * add pagination to kmip engine * update to pagination in config-ui engine * update sync engine to use pagination service * use pagination service in kv engine * use pagination service in ldap engine * use pagination in pki engine * update renaming clearDataset functions * link to jira VAULT-31721 * remove comment
This commit is contained in:
parent
f2041b00e5
commit
1fbbf9d76b
@ -21,6 +21,7 @@ export default class App extends Application {
|
||||
'namespace',
|
||||
{ 'app-router': 'router' },
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
'custom-messages',
|
||||
],
|
||||
@ -60,6 +61,7 @@ export default class App extends Application {
|
||||
'path-help',
|
||||
{ 'app-router': 'router' },
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
'secret-mount-path',
|
||||
],
|
||||
@ -78,7 +80,14 @@ export default class App extends Application {
|
||||
},
|
||||
ldap: {
|
||||
dependencies: {
|
||||
services: [{ 'app-router': 'router' }, 'store', 'secret-mount-path', 'flash-messages', 'auth'],
|
||||
services: [
|
||||
{ 'app-router': 'router' },
|
||||
'store',
|
||||
'pagination',
|
||||
'secret-mount-path',
|
||||
'flash-messages',
|
||||
'auth',
|
||||
],
|
||||
externalRoutes: {
|
||||
secrets: 'vault.cluster.secrets.backends',
|
||||
},
|
||||
@ -95,6 +104,7 @@ export default class App extends Application {
|
||||
{ 'app-router': 'router' },
|
||||
'secret-mount-path',
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
],
|
||||
externalRoutes: {
|
||||
@ -114,6 +124,7 @@ export default class App extends Application {
|
||||
{ 'app-router': 'router' },
|
||||
'secret-mount-path',
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
],
|
||||
externalRoutes: {
|
||||
@ -125,7 +136,7 @@ export default class App extends Application {
|
||||
},
|
||||
sync: {
|
||||
dependencies: {
|
||||
services: ['flash-messages', 'flags', { 'app-router': 'router' }, 'store', 'version'],
|
||||
services: ['flash-messages', 'flags', { 'app-router': 'router' }, 'store', 'pagination', 'version'],
|
||||
externalRoutes: {
|
||||
kvSecretOverview: 'vault.cluster.secrets.backend.kv.secret.index',
|
||||
clientCountOverview: 'vault.cluster.clients',
|
||||
|
||||
@ -28,7 +28,7 @@ export default Component.extend({
|
||||
console: service(),
|
||||
router: service(),
|
||||
controlGroup: service(),
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
'data-test-component': 'console/ui-panel',
|
||||
attributeBindings: ['data-test-component'],
|
||||
|
||||
@ -108,7 +108,7 @@ export default Component.extend({
|
||||
const currentRoute = owner.lookup(`router:main`).currentRouteName;
|
||||
|
||||
try {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
yield this.router.transitionTo(currentRoute);
|
||||
this.logAndOutput(null, { type: 'success', content: 'The current screen has been refreshed!' });
|
||||
} catch (error) {
|
||||
|
||||
@ -26,13 +26,13 @@ import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class GeneratedItemList extends Component {
|
||||
@service router;
|
||||
@service store;
|
||||
@service pagination;
|
||||
@tracked itemToDelete = null;
|
||||
|
||||
@action
|
||||
refreshItemList() {
|
||||
const route = getOwner(this).lookup(`route:${this.router.currentRouteName}`);
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
route.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ const VALID_TYPES_BY_PROVIDER = {
|
||||
azurekeyvault: ['rsa-2048', 'rsa-3072', 'rsa-4096'],
|
||||
};
|
||||
export default class KeymgmtDistribute extends Component {
|
||||
@service pagination;
|
||||
@service store;
|
||||
@service flashMessages;
|
||||
@service router;
|
||||
@ -191,7 +192,7 @@ export default class KeymgmtDistribute extends Component {
|
||||
.then(() => {
|
||||
this.flashMessages.success(`Successfully distributed key ${key} to ${provider}`);
|
||||
// update keys on provider model
|
||||
this.store.clearDataset('keymgmt/key');
|
||||
this.pagination.clearDataset('keymgmt/key');
|
||||
const providerModel = this.store.peekRecord('keymgmt/provider', provider);
|
||||
providerModel.fetchKeys(providerModel.keys?.meta?.currentPage || 1);
|
||||
this.args.onClose();
|
||||
|
||||
@ -5,10 +5,12 @@
|
||||
|
||||
import Mixin from '@ember/object/mixin';
|
||||
import removeRecord from 'vault/utils/remove-record';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
// removes Ember Data records from the cache when the model
|
||||
// changes or you move away from the current route
|
||||
export default Mixin.create({
|
||||
store: service(),
|
||||
modelPath: 'model',
|
||||
unloadModel() {
|
||||
const { modelPath } = this;
|
||||
|
||||
@ -44,7 +44,7 @@ const validations = {
|
||||
|
||||
@withModelValidations(validations)
|
||||
export default class KeymgmtProviderModel extends Model {
|
||||
@service store;
|
||||
@service pagination;
|
||||
@attr('string') backend;
|
||||
@attr('string', {
|
||||
label: 'Provider name',
|
||||
@ -128,7 +128,7 @@ export default class KeymgmtProviderModel extends Model {
|
||||
} else {
|
||||
// try unless capabilities returns false
|
||||
try {
|
||||
this.keys = await this.store.lazyPaginatedQuery('keymgmt/key', {
|
||||
this.keys = await this.pagination.lazyPaginatedQuery('keymgmt/key', {
|
||||
backend: this.backend,
|
||||
provider: this.name,
|
||||
responsePath: 'data.keys',
|
||||
|
||||
@ -8,12 +8,12 @@ import ListRoute from 'core/mixins/list-route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
|
||||
model(params) {
|
||||
const itemType = this.modelFor('vault.cluster.access.identity');
|
||||
const modelType = `identity/${itemType}-alias`;
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery(modelType, {
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
@ -38,12 +38,12 @@ export default Route.extend(ListRoute, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (!transition || transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -8,12 +8,12 @@ import ListRoute from 'core/mixins/list-route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
|
||||
model(params) {
|
||||
const itemType = this.modelFor('vault.cluster.access.identity');
|
||||
const modelType = `identity/${itemType}`;
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery(modelType, {
|
||||
responsePath: 'data.keys',
|
||||
page: params.page,
|
||||
@ -38,12 +38,12 @@ export default Route.extend(ListRoute, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -9,6 +9,7 @@ import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
pagination: service(),
|
||||
store: service(),
|
||||
|
||||
queryParams: {
|
||||
@ -26,7 +27,7 @@ export default Route.extend({
|
||||
const prefix = params.prefix || '';
|
||||
if (this.modelFor('vault.cluster.access.leases').canList) {
|
||||
return hash({
|
||||
leases: this.store
|
||||
leases: this.pagination
|
||||
.lazyPaginatedQuery('lease', {
|
||||
prefix,
|
||||
responsePath: 'data.keys',
|
||||
@ -104,7 +105,7 @@ export default Route.extend({
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -9,7 +9,7 @@ import { singularize } from 'ember-inflector';
|
||||
import ListRoute from 'vault/mixins/list-route';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
pathHelp: service('path-help'),
|
||||
|
||||
getMethodAndModelInfo() {
|
||||
@ -25,7 +25,7 @@ export default Route.extend(ListRoute, {
|
||||
const { page, pageFilter } = this.paramsFor(this.routeName);
|
||||
const modelType = `generated-${singularize(itemType)}-${type}`;
|
||||
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery(modelType, {
|
||||
responsePath: 'data.keys',
|
||||
page: page,
|
||||
@ -46,12 +46,12 @@ export default Route.extend(ListRoute, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -8,6 +8,7 @@ import Route from '@ember/routing/route';
|
||||
import UnloadModel from 'vault/mixins/unload-model-route';
|
||||
|
||||
export default Route.extend(UnloadModel, {
|
||||
pagination: service(),
|
||||
store: service(),
|
||||
|
||||
queryParams: {
|
||||
@ -27,7 +28,7 @@ export default Route.extend(UnloadModel, {
|
||||
|
||||
model(params) {
|
||||
if (this.version.hasNamespaces) {
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery('namespace', {
|
||||
responsePath: 'data.keys',
|
||||
page: Number(params?.page) || 1,
|
||||
@ -74,12 +75,12 @@ export default Route.extend(UnloadModel, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (!transition || transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -9,7 +9,7 @@ import ClusterRoute from 'vault/mixins/cluster-route';
|
||||
import ListRoute from 'core/mixins/list-route';
|
||||
|
||||
export default Route.extend(ClusterRoute, ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
version: service(),
|
||||
|
||||
shouldReturnEmptyModel(policyType, version) {
|
||||
@ -21,7 +21,7 @@ export default Route.extend(ClusterRoute, ListRoute, {
|
||||
if (this.shouldReturnEmptyModel(policyType, this.version)) {
|
||||
return;
|
||||
}
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery(`policy/${policyType}`, {
|
||||
page: params.page,
|
||||
pageFilter: params.pageFilter,
|
||||
@ -65,12 +65,12 @@ export default Route.extend(ClusterRoute, ListRoute, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (!transition || transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -30,6 +30,7 @@ function getValidPage(pageParam) {
|
||||
}
|
||||
|
||||
export default Route.extend({
|
||||
pagination: service(),
|
||||
store: service(),
|
||||
templateName: 'vault/cluster/secrets/backend/list',
|
||||
pathHelp: service('path-help'),
|
||||
@ -131,7 +132,7 @@ export default Route.extend({
|
||||
|
||||
return hash({
|
||||
secret,
|
||||
secrets: this.store
|
||||
secrets: this.pagination
|
||||
.lazyPaginatedQuery(modelType, {
|
||||
id: secret,
|
||||
backend,
|
||||
@ -163,7 +164,7 @@ export default Route.extend({
|
||||
const has404 = this.has404;
|
||||
// only clear store cache if this is a new model
|
||||
if (secret !== controller?.baseKey?.id) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
controller.set('hasModel', true);
|
||||
controller.setProperties({
|
||||
@ -220,12 +221,12 @@ export default Route.extend({
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -3,9 +3,7 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Store, { CacheHandler } from '@ember-data/store';
|
||||
import RequestManager from '@ember-data/request';
|
||||
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
|
||||
import Service, { service } from '@ember/service';
|
||||
import { schedule } from '@ember/runloop';
|
||||
import { resolve, Promise } from 'rsvp';
|
||||
import { dasherize } from '@ember/string';
|
||||
@ -17,10 +15,6 @@ import sortObjects from 'vault/utils/sort-objects';
|
||||
|
||||
const { DEFAULT_PAGE_SIZE } = config.APP;
|
||||
|
||||
export function normalizeModelName(modelName) {
|
||||
return dasherize(modelName);
|
||||
}
|
||||
|
||||
export function keyForCache(query) {
|
||||
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
|
||||
// we want to ignore size, page, responsePath, and pageFilter in the cacheKey
|
||||
@ -34,16 +28,8 @@ export function keyForCache(query) {
|
||||
return JSON.stringify(cacheKeyObject);
|
||||
}
|
||||
|
||||
export default class StoreService extends Store {
|
||||
requestManager = new RequestManager();
|
||||
|
||||
constructor(args) {
|
||||
super(args);
|
||||
// If at some point we no longer need an extended store, we can remove the @ember-data/legacy-compat dep
|
||||
// See: https://api.emberjs.com/ember-data/4.12/modules/@ember-data%2Frequest
|
||||
this.requestManager.use([LegacyNetworkHandler]);
|
||||
this.requestManager.useCache(CacheHandler);
|
||||
}
|
||||
export default class Pagination extends Service {
|
||||
@service store;
|
||||
|
||||
lazyCaches = new Map();
|
||||
|
||||
@ -51,7 +37,7 @@ export default class StoreService extends Store {
|
||||
const cacheKey = keyForCache(key);
|
||||
const cache = this.lazyCacheForModel(modelName) || new Map();
|
||||
cache.set(cacheKey, value);
|
||||
const modelKey = normalizeModelName(modelName);
|
||||
const modelKey = dasherize(modelName);
|
||||
this.lazyCaches.set(modelKey, cache);
|
||||
}
|
||||
|
||||
@ -64,7 +50,7 @@ export default class StoreService extends Store {
|
||||
}
|
||||
|
||||
lazyCacheForModel(modelName) {
|
||||
return this.lazyCaches.get(normalizeModelName(modelName));
|
||||
return this.lazyCaches.get(dasherize(modelName));
|
||||
}
|
||||
|
||||
// This is the public interface for the store extension - to be used just
|
||||
@ -83,8 +69,8 @@ export default class StoreService extends Store {
|
||||
const skipCache = query.skipCache;
|
||||
// We don't want skipCache to be part of the actual query key, so remove it
|
||||
delete query.skipCache;
|
||||
const adapter = this.adapterFor(modelType);
|
||||
const modelName = normalizeModelName(modelType);
|
||||
const adapter = this.store.adapterFor(modelType);
|
||||
const modelName = dasherize(modelType);
|
||||
const dataCache = skipCache ? this.clearDataset(modelName) : this.getDataset(modelName, query);
|
||||
const responsePath = query.responsePath;
|
||||
assert('responsePath is required', responsePath);
|
||||
@ -97,9 +83,9 @@ export default class StoreService extends Store {
|
||||
return resolve(this.fetchPage(modelName, query));
|
||||
}
|
||||
return adapter
|
||||
.query(this, { modelName }, query, null, adapterOptions)
|
||||
.query(this.store, { modelName }, query, null, adapterOptions)
|
||||
.then((response) => {
|
||||
const serializer = this.serializerFor(modelName);
|
||||
const serializer = this.store.serializerFor(modelName);
|
||||
const datasetHelper = serializer.extractLazyPaginatedData;
|
||||
const dataset = datasetHelper
|
||||
? datasetHelper.call(serializer, response)
|
||||
@ -162,20 +148,16 @@ export default class StoreService extends Store {
|
||||
// pushes records into the store and returns the result
|
||||
fetchPage(modelName, query) {
|
||||
const response = this.constructResponse(modelName, query);
|
||||
this.unloadAll(modelName);
|
||||
this.store.unloadAll(modelName);
|
||||
return new Promise((resolve) => {
|
||||
// push subset of records into the store
|
||||
schedule('destroy', () => {
|
||||
this.push(
|
||||
this.serializerFor(modelName).normalizeResponse(
|
||||
this,
|
||||
this.modelFor(modelName),
|
||||
response,
|
||||
null,
|
||||
'query'
|
||||
)
|
||||
this.store.push(
|
||||
this.store
|
||||
.serializerFor(modelName)
|
||||
.normalizeResponse(this.store, this.store.modelFor(modelName), response, null, 'query')
|
||||
);
|
||||
const model = this.peekAll(modelName).slice();
|
||||
const model = this.store.peekAll(modelName).slice();
|
||||
model.set('meta', response.meta);
|
||||
resolve(model);
|
||||
});
|
||||
@ -1,10 +1,10 @@
|
||||
# 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.
|
||||
Our custom `pagination` 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. It was originally a custom method in our `store` service that extended the ember-data `store` but now is it's own `pagination` service.
|
||||
|
||||
## 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`
|
||||
Rather than use `store.query`, use `pagination.lazyPaginatedQuery`. It generally uses the same inputs, but accepts additional keys in the query object `size`, `page`, `responsePath`, `pageFilter`
|
||||
|
||||
### Before
|
||||
|
||||
@ -23,12 +23,12 @@ export default class ExampleRoute extends Route {
|
||||
|
||||
```js
|
||||
export default class ExampleRoute extends Route {
|
||||
@service store;
|
||||
@service pagination;
|
||||
|
||||
model(params) {
|
||||
const { page, pageFilter, secret } = params;
|
||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
return this.store.lazyPaginatedQuery('secret', {
|
||||
return this.pagination.lazyPaginatedQuery('secret', {
|
||||
backend,
|
||||
id: secret,
|
||||
size,
|
||||
@ -47,11 +47,11 @@ In order to interrupt the regular serialization when using `lazyPaginatedData`,
|
||||
|
||||
## 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.
|
||||
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 `pagination.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](https://github.com/tracked-tools/tracked-built-ins/tree/master) within the service. `store.lazyCaches` is actually a Map of [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), 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:
|
||||
When using the `lazyPaginatedQuery` method, the full response is cached in a [tracked Map](https://github.com/tracked-tools/tracked-built-ins/tree/master) within the service. `pagination.lazyCaches` is actually a Map of [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), 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 `pagination.lazyCaches` looks like this:
|
||||
|
||||
```
|
||||
lazyCaches = new Map({
|
||||
@ -61,7 +61,7 @@ lazyCaches = new 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:
|
||||
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 pagination.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({
|
||||
|
||||
@ -25,6 +25,7 @@ import { isAfter } from 'date-fns';
|
||||
export default class MessagesList extends Component {
|
||||
@service('app-router') router;
|
||||
@service store;
|
||||
@service pagination;
|
||||
@service flashMessages;
|
||||
@service customMessages;
|
||||
@service namespace;
|
||||
@ -75,7 +76,7 @@ export default class MessagesList extends Component {
|
||||
const { isNew } = this.args.message;
|
||||
const { id, title } = yield this.args.message.save();
|
||||
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} ${title} message.`);
|
||||
this.store.clearDataset('config-ui/message');
|
||||
this.pagination.clearDataset('config-ui/message');
|
||||
this.customMessages.fetchMessages(this.namespace.path);
|
||||
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
|
||||
}
|
||||
|
||||
@ -19,17 +19,17 @@ import errorMessage from 'vault/utils/error-message';
|
||||
*/
|
||||
|
||||
export default class MessageDetails extends Component {
|
||||
@service store;
|
||||
@service('app-router') router;
|
||||
@service flashMessages;
|
||||
@service customMessages;
|
||||
@service namespace;
|
||||
@service pagination;
|
||||
|
||||
@action
|
||||
async deleteMessage() {
|
||||
try {
|
||||
this.store.clearDataset('config-ui/message');
|
||||
await this.args.message.destroyRecord(this.args.message.id);
|
||||
this.pagination.clearDataset('config-ui/message');
|
||||
this.router.transitionTo('vault.cluster.config-ui.messages');
|
||||
this.customMessages.fetchMessages(this.namespace.path);
|
||||
this.flashMessages.success(`Successfully deleted ${this.args.message.title}.`);
|
||||
|
||||
@ -23,11 +23,11 @@ import errorMessage from 'vault/utils/error-message';
|
||||
*/
|
||||
|
||||
export default class MessagesList extends Component {
|
||||
@service store;
|
||||
@service('app-router') router;
|
||||
@service customMessages;
|
||||
@service flashMessages;
|
||||
@service namespace;
|
||||
@service customMessages;
|
||||
@service pagination;
|
||||
@service('app-router') router;
|
||||
|
||||
@tracked showMaxMessageModal = false;
|
||||
@tracked messageToDelete = null;
|
||||
@ -90,8 +90,8 @@ export default class MessagesList extends Component {
|
||||
@task
|
||||
*deleteMessage(message) {
|
||||
try {
|
||||
this.store.clearDataset('config-ui/message');
|
||||
yield message.destroyRecord(message.id);
|
||||
this.pagination.clearDataset('config-ui/message');
|
||||
this.router.transitionTo('vault.cluster.config-ui.messages');
|
||||
this.customMessages.fetchMessages(this.namespace.path);
|
||||
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
||||
|
||||
@ -16,7 +16,16 @@ export default class ConfigUiEngine extends Engine {
|
||||
modulePrefix = modulePrefix;
|
||||
Resolver = Resolver;
|
||||
dependencies = {
|
||||
services: ['auth', 'store', 'flash-messages', 'namespace', 'app-router', 'version', 'custom-messages'],
|
||||
services: [
|
||||
'auth',
|
||||
'store',
|
||||
'pagination',
|
||||
'flash-messages',
|
||||
'namespace',
|
||||
'app-router',
|
||||
'version',
|
||||
'custom-messages',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ import { service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
export default class MessagesRoute extends Route {
|
||||
@service store;
|
||||
@service pagination;
|
||||
|
||||
queryParams = {
|
||||
page: {
|
||||
@ -38,7 +38,7 @@ export default class MessagesRoute extends Route {
|
||||
if (status === 'active') active = true;
|
||||
if (status === 'inactive') active = false;
|
||||
|
||||
const messages = this.store
|
||||
const messages = this.pagination
|
||||
.lazyPaginatedQuery('config-ui/message', {
|
||||
authenticated,
|
||||
pageFilter: filter,
|
||||
|
||||
@ -35,12 +35,12 @@ export default Mixin.create({
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -5,15 +5,14 @@
|
||||
|
||||
import Engine from 'ember-engines/engine';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import Resolver from './resolver';
|
||||
import Resolver from 'ember-resolver';
|
||||
import config from './config/environment';
|
||||
|
||||
const { modulePrefix } = config;
|
||||
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
|
||||
const Eng = Engine.extend({
|
||||
modulePrefix,
|
||||
Resolver,
|
||||
dependencies: {
|
||||
export default class KmipEngine extends Engine {
|
||||
modulePrefix = modulePrefix;
|
||||
Resolver = Resolver;
|
||||
dependencies = {
|
||||
services: [
|
||||
'auth',
|
||||
'download',
|
||||
@ -22,13 +21,12 @@ const Eng = Engine.extend({
|
||||
'path-help',
|
||||
'app-router',
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
'secret-mount-path',
|
||||
],
|
||||
externalRoutes: ['secrets'],
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
loadInitializers(Eng, modulePrefix);
|
||||
|
||||
export default Eng;
|
||||
loadInitializers(KmipEngine, modulePrefix);
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Resolver from 'ember-resolver';
|
||||
|
||||
export default Resolver;
|
||||
@ -8,7 +8,7 @@ import ListRoute from 'core/mixins/list-route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
secretMountPath: service(),
|
||||
credParams() {
|
||||
const { role_name: role, scope_name: scope } = this.paramsFor('credentials');
|
||||
@ -19,7 +19,7 @@ export default Route.extend(ListRoute, {
|
||||
},
|
||||
model(params) {
|
||||
const { role, scope } = this.credParams();
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery('kmip/credential', {
|
||||
role,
|
||||
scope,
|
||||
|
||||
@ -8,7 +8,7 @@ import ListRoute from 'core/mixins/list-route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
secretMountPath: service(),
|
||||
pathHelp: service(),
|
||||
scope() {
|
||||
@ -18,7 +18,7 @@ export default Route.extend(ListRoute, {
|
||||
return this.pathHelp.hydrateModel('kmip/role', this.secretMountPath.currentPath);
|
||||
},
|
||||
model(params) {
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery('kmip/role', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
scope: this.scope(),
|
||||
|
||||
@ -8,10 +8,10 @@ import { service } from '@ember/service';
|
||||
import ListRoute from 'core/mixins/list-route';
|
||||
|
||||
export default Route.extend(ListRoute, {
|
||||
store: service(),
|
||||
pagination: service(),
|
||||
secretMountPath: service(),
|
||||
model(params) {
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery('kmip/scope', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
responsePath: 'data.keys',
|
||||
@ -31,12 +31,12 @@ export default Route.extend(ListRoute, {
|
||||
willTransition(transition) {
|
||||
window.scrollTo(0, 0);
|
||||
if (transition.targetName !== this.routeName) {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
reload() {
|
||||
this.store.clearDataset();
|
||||
this.pagination.clearDataset();
|
||||
this.refresh();
|
||||
},
|
||||
},
|
||||
|
||||
@ -27,7 +27,7 @@ import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
|
||||
export default class KvListPageComponent extends Component {
|
||||
@service flashMessages;
|
||||
@service('app-router') router;
|
||||
@service store;
|
||||
@service pagination;
|
||||
|
||||
@tracked secretPath;
|
||||
@tracked metadataToDelete = null; // set to the metadata intended to delete
|
||||
@ -57,7 +57,7 @@ export default class KvListPageComponent extends Component {
|
||||
try {
|
||||
// The model passed in is a kv/metadata model
|
||||
await model.destroyRecord();
|
||||
this.store.clearDataset('kv/metadata'); // Clear out the store cache so that the metadata/list view is updated.
|
||||
this.pagination.clearDataset('kv/metadata'); // Clear out the pagination cache so that the metadata/list view is updated.
|
||||
const message = `Successfully deleted the metadata and all version data of the secret ${model.fullSecretPath}.`;
|
||||
this.flashMessages.success(message);
|
||||
// if you've deleted a secret from within a directory, transition to its parent directory.
|
||||
|
||||
@ -39,6 +39,7 @@ export default class KvSecretMetadataDetails extends Component {
|
||||
@service flashMessages;
|
||||
@service('app-router') router;
|
||||
@service store;
|
||||
@service pagination;
|
||||
|
||||
@tracked error = null;
|
||||
@tracked customMetadataFromData = null;
|
||||
@ -54,7 +55,7 @@ export default class KvSecretMetadataDetails extends Component {
|
||||
const adapter = this.store.adapterFor('kv/metadata');
|
||||
try {
|
||||
await adapter.deleteMetadata(backend, path);
|
||||
this.store.clearDataset('kv/metadata'); // Clear out the store cache so that the metadata/list view is updated.
|
||||
this.pagination.clearDataset('kv/metadata'); // Clear out the store cache so that the metadata/list view is updated.
|
||||
this.flashMessages.success(
|
||||
`Successfully deleted the metadata and all version data for the secret ${path}.`
|
||||
);
|
||||
|
||||
@ -29,7 +29,7 @@ export default class KvSecretCreate extends Component {
|
||||
@service controlGroup;
|
||||
@service flashMessages;
|
||||
@service('app-router') router;
|
||||
@service store;
|
||||
@service pagination;
|
||||
|
||||
@tracked showJsonView = false;
|
||||
@tracked errorMessage;
|
||||
@ -60,7 +60,7 @@ export default class KvSecretCreate extends Component {
|
||||
try {
|
||||
// try saving secret data first
|
||||
yield secret.save();
|
||||
this.store.clearDataset('kv/metadata'); // Clear out the store cache so that the metadata/list view is updated.
|
||||
this.pagination.clearDataset('kv/metadata'); // Clear out the pagination cache so that the metadata/list view is updated.
|
||||
this.flashMessages.success(`Successfully saved secret data for: ${secret.path}.`);
|
||||
} catch (error) {
|
||||
let message = errorMessage(error);
|
||||
|
||||
@ -25,6 +25,7 @@ export default class KvEngine extends Engine {
|
||||
'app-router',
|
||||
'secret-mount-path',
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
],
|
||||
externalRoutes: ['secrets', 'syncDestination'],
|
||||
|
||||
@ -9,7 +9,7 @@ import { hash } from 'rsvp';
|
||||
import { pathIsDirectory, breadcrumbsForSecret } from 'kv/utils/kv-breadcrumbs';
|
||||
|
||||
export default class KvSecretsListRoute extends Route {
|
||||
@service store;
|
||||
@service pagination;
|
||||
@service('app-router') router;
|
||||
@service secretMountPath;
|
||||
|
||||
@ -23,7 +23,7 @@ export default class KvSecretsListRoute extends Route {
|
||||
};
|
||||
|
||||
async fetchMetadata(backend, pathToSecret, params) {
|
||||
return await this.store
|
||||
return await this.pagination
|
||||
.lazyPaginatedQuery('kv/metadata', {
|
||||
backend,
|
||||
responsePath: 'data.keys',
|
||||
|
||||
@ -15,7 +15,7 @@ import type LdapRoleModel from 'vault/models/ldap/role';
|
||||
import { Breadcrumb, ValidationMap } from 'vault/vault/app-types';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
|
||||
interface Args {
|
||||
model: LdapRoleModel;
|
||||
@ -31,7 +31,7 @@ interface RoleTypeOption {
|
||||
export default class LdapCreateAndEditRolePageComponent extends Component<Args> {
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
@tracked modelValidations: ValidationMap | null = null;
|
||||
@tracked invalidFormMessage = '';
|
||||
@ -71,7 +71,7 @@ export default class LdapCreateAndEditRolePageComponent extends Component<Args>
|
||||
yield model.save();
|
||||
this.flashMessages.success(`Successfully ${action} the role ${model.name}`);
|
||||
if (action === 'created') {
|
||||
this.store.clearDataset('ldap/role');
|
||||
this.pagination.clearDataset('ldap/role');
|
||||
}
|
||||
this.router.transitionTo(
|
||||
'vault.cluster.secrets.backend.ldap.roles.role.details',
|
||||
|
||||
@ -14,7 +14,7 @@ import type LdapRoleModel from 'vault/models/ldap/role';
|
||||
import { Breadcrumb } from 'vault/vault/app-types';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
|
||||
interface Args {
|
||||
model: LdapRoleModel;
|
||||
@ -24,14 +24,14 @@ interface Args {
|
||||
export default class LdapRoleDetailsPageComponent extends Component<Args> {
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
@action
|
||||
async delete() {
|
||||
try {
|
||||
await this.args.model.destroyRecord();
|
||||
this.flashMessages.success('Role deleted successfully.');
|
||||
this.store.clearDataset('ldap/role');
|
||||
this.pagination.clearDataset('ldap/role');
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.ldap.roles');
|
||||
} catch (error) {
|
||||
const message = errorMessage(error, 'Unable to delete role. Please try again or contact support.');
|
||||
|
||||
@ -14,7 +14,7 @@ import type SecretEngineModel from 'vault/models/secret-engine';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type { Breadcrumb, EngineOwner } from 'vault/vault/app-types';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
interface Args {
|
||||
@ -28,7 +28,7 @@ interface Args {
|
||||
export default class LdapRolesPageComponent extends Component<Args> {
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@tracked credsToRotate: LdapRoleModel | null = null;
|
||||
@tracked roleToDelete: LdapRoleModel | null = null;
|
||||
|
||||
@ -64,7 +64,7 @@ export default class LdapRolesPageComponent extends Component<Args> {
|
||||
try {
|
||||
const message = `Successfully deleted role ${model.name}.`;
|
||||
await model.destroyRecord();
|
||||
this.store.clearDataset('ldap/role');
|
||||
this.pagination.clearDataset('ldap/role');
|
||||
this.router.transitionTo('vault.cluster.secrets.backend.ldap.roles');
|
||||
this.flashMessages.success(message);
|
||||
} catch (error) {
|
||||
|
||||
@ -14,7 +14,7 @@ export default class LdapEngine extends Engine {
|
||||
modulePrefix = modulePrefix;
|
||||
Resolver = Resolver;
|
||||
dependencies = {
|
||||
services: ['app-router', 'store', 'secret-mount-path', 'flash-messages', 'auth'],
|
||||
services: ['app-router', 'store', 'pagination', 'secret-mount-path', 'flash-messages', 'auth'],
|
||||
externalRoutes: ['secrets'],
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { withConfig } from 'core/decorators/fetch-secrets-engine-config';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type SecretMountPath from 'vault/services/secret-mount-path';
|
||||
import type Transition from '@ember/routing/transition';
|
||||
import type LdapRoleModel from 'vault/models/ldap/role';
|
||||
@ -36,6 +37,7 @@ interface LdapRolesRouteParams {
|
||||
@withConfig('ldap/config')
|
||||
export default class LdapRolesRoute extends Route {
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service declare readonly secretMountPath: SecretMountPath;
|
||||
|
||||
declare promptConfig: boolean;
|
||||
@ -54,7 +56,7 @@ export default class LdapRolesRoute extends Route {
|
||||
return hash({
|
||||
backendModel,
|
||||
promptConfig: this.promptConfig,
|
||||
roles: this.store.lazyPaginatedQuery(
|
||||
roles: this.pagination.lazyPaginatedQuery(
|
||||
'ldap/role',
|
||||
{
|
||||
backend: backendModel.id,
|
||||
|
||||
@ -25,6 +25,7 @@ export default class PkiEngine extends Engine {
|
||||
'app-router',
|
||||
'secret-mount-path',
|
||||
'store',
|
||||
'pagination',
|
||||
'version',
|
||||
],
|
||||
externalRoutes: ['secrets', 'secretsListRootConfiguration', 'externalMountIssuer'],
|
||||
|
||||
@ -11,8 +11,9 @@ import { getCliMessage } from 'pki/routes/overview';
|
||||
|
||||
@withConfig()
|
||||
export default class PkiCertificatesIndexRoute extends Route {
|
||||
@service store;
|
||||
@service pagination;
|
||||
@service secretMountPath;
|
||||
@service store; // used by @withConfig decorator
|
||||
|
||||
queryParams = {
|
||||
page: {
|
||||
@ -23,7 +24,7 @@ export default class PkiCertificatesIndexRoute extends Route {
|
||||
async fetchCertificates(params) {
|
||||
try {
|
||||
const page = Number(params.page) || 1;
|
||||
return await this.store.lazyPaginatedQuery('pki/certificate/base', {
|
||||
return await this.pagination.lazyPaginatedQuery('pki/certificate/base', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
responsePath: 'data.keys',
|
||||
page,
|
||||
|
||||
@ -7,12 +7,12 @@ import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default class PkiIssuersListRoute extends Route {
|
||||
@service store;
|
||||
@service pagination;
|
||||
@service secretMountPath;
|
||||
|
||||
model(params) {
|
||||
const page = Number(params.page) || 1;
|
||||
return this.store
|
||||
return this.pagination
|
||||
.lazyPaginatedQuery('pki/issuer', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
responsePath: 'data.keys',
|
||||
|
||||
@ -11,8 +11,9 @@ import { PKI_DEFAULT_EMPTY_STATE_MSG } from 'pki/routes/overview';
|
||||
|
||||
@withConfig()
|
||||
export default class PkiKeysIndexRoute extends Route {
|
||||
@service pagination;
|
||||
@service secretMountPath;
|
||||
@service store;
|
||||
@service store; // used by @withConfig decorator
|
||||
|
||||
queryParams = {
|
||||
page: {
|
||||
@ -25,7 +26,7 @@ export default class PkiKeysIndexRoute extends Route {
|
||||
return hash({
|
||||
hasConfig: this.pkiMountHasConfig,
|
||||
parentModel: this.modelFor('keys'),
|
||||
keyModels: this.store
|
||||
keyModels: this.pagination
|
||||
.lazyPaginatedQuery('pki/key', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
responsePath: 'data.keys',
|
||||
|
||||
@ -10,8 +10,9 @@ import { hash } from 'rsvp';
|
||||
import { getCliMessage } from 'pki/routes/overview';
|
||||
@withConfig()
|
||||
export default class PkiRolesIndexRoute extends Route {
|
||||
@service store;
|
||||
@service store; // used by @withConfig decorator
|
||||
@service secretMountPath;
|
||||
@service pagination;
|
||||
|
||||
queryParams = {
|
||||
page: {
|
||||
@ -22,7 +23,7 @@ export default class PkiRolesIndexRoute extends Route {
|
||||
async fetchRoles(params) {
|
||||
try {
|
||||
const page = Number(params.page) || 1;
|
||||
return await this.store.lazyPaginatedQuery('pki/role', {
|
||||
return await this.pagination.lazyPaginatedQuery('pki/role', {
|
||||
backend: this.secretMountPath.currentPath,
|
||||
responsePath: 'data.keys',
|
||||
page,
|
||||
|
||||
@ -10,7 +10,7 @@ import errorMessage from 'vault/utils/error-message';
|
||||
|
||||
import type SyncDestinationModel from 'vault/models/sync/destination';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
|
||||
interface Args {
|
||||
@ -19,7 +19,7 @@ interface Args {
|
||||
|
||||
export default class DestinationsTabsToolbar extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@action
|
||||
@ -28,7 +28,7 @@ export default class DestinationsTabsToolbar extends Component<Args> {
|
||||
const { destination } = this.args;
|
||||
const message = `Destination ${destination.name} has been queued for deletion.`;
|
||||
await destination.destroyRecord();
|
||||
this.store.clearDataset('sync/destination');
|
||||
this.pagination.clearDataset('sync/destination');
|
||||
this.router.transitionTo('vault.cluster.sync.secrets.overview');
|
||||
this.flashMessages.success(message);
|
||||
} catch (error) {
|
||||
|
||||
@ -14,7 +14,7 @@ import { next } from '@ember/runloop';
|
||||
|
||||
import type SyncDestinationModel from 'vault/vault/models/sync/destination';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type { EngineOwner } from 'vault/vault/app-types';
|
||||
import type { SyncDestinationName, SyncDestinationType } from 'vault/vault/helpers/sync-destinations';
|
||||
@ -28,7 +28,7 @@ interface Args {
|
||||
|
||||
export default class SyncSecretsDestinationsPageComponent extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@tracked destinationToDelete: SyncDestinationModel | null = null;
|
||||
@ -98,7 +98,7 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
|
||||
const { name } = destination;
|
||||
const message = `Destination ${name} has been queued for deletion.`;
|
||||
await destination.destroyRecord();
|
||||
this.store.clearDataset('sync/destination');
|
||||
this.pagination.clearDataset('sync/destination');
|
||||
this.router.transitionTo('vault.cluster.sync.secrets.overview');
|
||||
this.flashMessages.success(message);
|
||||
} catch (error) {
|
||||
|
||||
@ -15,7 +15,7 @@ import type SyncDestinationModel from 'vault/models/sync/destination';
|
||||
import { ValidationMap } from 'vault/vault/app-types';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
|
||||
interface Args {
|
||||
destination: SyncDestinationModel;
|
||||
@ -24,7 +24,7 @@ interface Args {
|
||||
export default class DestinationsCreateForm extends Component<Args> {
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
@tracked modelValidations: ValidationMap | null = null;
|
||||
@tracked invalidFormMessage = '';
|
||||
@ -95,7 +95,7 @@ export default class DestinationsCreateForm extends Component<Args> {
|
||||
// if the user then attempts to update the record the credential will get overwritten with the masked placeholder value
|
||||
// since the record will be fetched from the details route we can safely unload it to avoid the aforementioned issue
|
||||
destination.unloadRecord();
|
||||
this.store.clearDataset('sync/destination');
|
||||
this.pagination.clearDataset('sync/destination');
|
||||
}
|
||||
this.router.transitionTo(
|
||||
'vault.cluster.sync.secrets.destinations.destination.details',
|
||||
|
||||
@ -13,7 +13,7 @@ import errorMessage from 'vault/utils/error-message';
|
||||
import SyncDestinationModel from 'vault/vault/models/sync/destination';
|
||||
import type SyncAssociationModel from 'vault/vault/models/sync/association';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type { EngineOwner } from 'vault/vault/app-types';
|
||||
|
||||
@ -24,7 +24,7 @@ interface Args {
|
||||
|
||||
export default class SyncSecretsDestinationsPageComponent extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
|
||||
@tracked secretToUnsync: SyncAssociationModel | null = null;
|
||||
@ -41,7 +41,7 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
|
||||
@action
|
||||
refreshRoute() {
|
||||
// refresh route to update displayed secrets
|
||||
this.store.clearDataset('sync/association');
|
||||
this.pagination.clearDataset('sync/association');
|
||||
this.router.transitionTo(
|
||||
'vault.cluster.sync.secrets.destinations.destination.secrets',
|
||||
this.args.destination.type,
|
||||
|
||||
@ -14,6 +14,7 @@ import errorMessage from 'vault/utils/error-message';
|
||||
import type SyncDestinationModel from 'vault/models/sync/destination';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type { SearchSelectOption } from 'vault/vault/app-types';
|
||||
|
||||
@ -25,6 +26,7 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
@ -46,7 +48,7 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
||||
}
|
||||
|
||||
willDestroy(): void {
|
||||
this.store.clearDataset('sync/association');
|
||||
this.pagination.clearDataset('sync/association');
|
||||
super.willDestroy();
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ export default class SyncEngine extends Engine {
|
||||
modulePrefix = modulePrefix;
|
||||
Resolver = Resolver;
|
||||
dependencies = {
|
||||
services: ['flash-messages', 'flags', 'app-router', 'store', 'version'],
|
||||
services: ['flash-messages', 'flags', 'app-router', 'store', 'pagination', 'version'],
|
||||
externalRoutes: ['kvSecretOverview', 'clientCountOverview'],
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type SyncDestinationModel from 'vault/vault/models/sync/destination';
|
||||
import type SyncAssociationModel from 'vault/vault/models/sync/association';
|
||||
import type Controller from '@ember/controller';
|
||||
@ -27,7 +27,7 @@ interface SyncDestinationSecretsController extends Controller {
|
||||
}
|
||||
|
||||
export default class SyncDestinationSecretsRoute extends Route {
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
|
||||
queryParams = {
|
||||
page: {
|
||||
@ -39,7 +39,7 @@ export default class SyncDestinationSecretsRoute extends Route {
|
||||
const destination = this.modelFor('secrets.destinations.destination') as SyncDestinationModel;
|
||||
return hash({
|
||||
destination,
|
||||
associations: this.store.lazyPaginatedQuery('sync/association', {
|
||||
associations: this.pagination.lazyPaginatedQuery('sync/association', {
|
||||
responsePath: 'data.keys',
|
||||
page: Number(params.page) || 1,
|
||||
destinationType: destination.type,
|
||||
|
||||
@ -7,7 +7,7 @@ import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type { ModelFrom } from 'vault/vault/route';
|
||||
import type SyncDestinationModel from 'vault/vault/models/sync/destination';
|
||||
@ -33,7 +33,7 @@ interface SyncSecretsDestinationsController extends Controller {
|
||||
}
|
||||
|
||||
export default class SyncSecretsDestinationsIndexRoute extends Route {
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service('app-router') declare readonly router: RouterService;
|
||||
|
||||
queryParams = {
|
||||
@ -73,7 +73,7 @@ export default class SyncSecretsDestinationsIndexRoute extends Route {
|
||||
async model(params: SyncSecretsDestinationsIndexRouteParams) {
|
||||
const { name, type, page } = params;
|
||||
return hash({
|
||||
destinations: this.store.lazyPaginatedQuery('sync/destination', {
|
||||
destinations: this.pagination.lazyPaginatedQuery('sync/destination', {
|
||||
page: Number(page) || 1,
|
||||
pageFilter: (dataset: Array<SyncDestinationModel>) => this.filterData(dataset, name, type),
|
||||
responsePath: 'data.keys',
|
||||
|
||||
@ -47,7 +47,7 @@ module('Integration | Component | sync | Secrets::DestinationHeader', function (
|
||||
assert.expect(3);
|
||||
|
||||
const transitionStub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo');
|
||||
const clearDatasetStub = sinon.stub(this.store, 'clearDataset');
|
||||
const clearDatasetStub = sinon.stub(this.owner.lookup('service:pagination'), 'clearDataset');
|
||||
|
||||
this.server.delete('/sys/sync/destinations/aws-sm/us-west-1', () => {
|
||||
assert.ok(true, 'Request made to delete destination');
|
||||
|
||||
@ -56,7 +56,7 @@ module('Integration | Component | sync | Page::Destinations', function (hooks) {
|
||||
};
|
||||
|
||||
this.transitionStub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo');
|
||||
this.clearDatasetStub = sinon.stub(store, 'clearDataset');
|
||||
this.clearDatasetStub = sinon.stub(this.owner.lookup('service:pagination'), 'clearDataset');
|
||||
});
|
||||
|
||||
test('it should render header and tabs', async function (assert) {
|
||||
|
||||
@ -24,7 +24,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.transitionStub = sinon.stub(this.owner.lookup('service:router'), 'transitionTo');
|
||||
this.clearDatasetStub = sinon.stub(this.store, 'clearDataset');
|
||||
this.clearDatasetStub = sinon.stub(this.owner.lookup('service:pagination'), 'clearDataset');
|
||||
|
||||
this.renderFormComponent = () => {
|
||||
return render(hbs` <Secrets::Page::Destinations::CreateAndEdit @destination={{this.model}} />`, {
|
||||
|
||||
@ -140,7 +140,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::Destinatio
|
||||
});
|
||||
|
||||
test('it should clear sync associations from store in willDestroy hook', async function (assert) {
|
||||
const clearDatasetStub = sinon.stub(this.store, 'clearDataset');
|
||||
const clearDatasetStub = sinon.stub(this.owner.lookup('service:pagination'), 'clearDataset');
|
||||
|
||||
this.renderComponent = true;
|
||||
await render(
|
||||
|
||||
@ -15,6 +15,7 @@ module('Unit | Adapter | sync | association', function (hooks) {
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
this.pagination = this.owner.lookup('service:pagination');
|
||||
|
||||
this.params = [
|
||||
{ type: 'aws-sm', name: 'us-west-1' },
|
||||
@ -50,7 +51,7 @@ module('Unit | Adapter | sync | association', function (hooks) {
|
||||
return associationsResponse(schema, req);
|
||||
});
|
||||
|
||||
await this.store.lazyPaginatedQuery('sync/association', {
|
||||
await this.pagination.lazyPaginatedQuery('sync/association', {
|
||||
responsePath: 'data.keys',
|
||||
page: 1,
|
||||
destinationType: 'aws-sm',
|
||||
|
||||
288
ui/tests/unit/services/pagination-test.js
Normal file
288
ui/tests/unit/services/pagination-test.js
Normal file
@ -0,0 +1,288 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { keyForCache } from 'vault/services/pagination';
|
||||
import { dasherize } from '@ember/string';
|
||||
import clamp from 'vault/utils/clamp';
|
||||
import config from 'vault/config/environment';
|
||||
import Sinon from 'sinon';
|
||||
|
||||
const { DEFAULT_PAGE_SIZE } = config.APP;
|
||||
|
||||
module('Unit | Service | pagination', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.pagination = this.owner.lookup('service:pagination');
|
||||
this.store = this.owner.lookup('service:store');
|
||||
});
|
||||
|
||||
test('pagination.setLazyCacheForModel', function (assert) {
|
||||
const modelName = 'someModel';
|
||||
const key = {
|
||||
id: '',
|
||||
backend: 'database',
|
||||
responsePath: 'data.keys',
|
||||
page: 1,
|
||||
pageFilter: null,
|
||||
size: 15,
|
||||
};
|
||||
const value = {
|
||||
response: {
|
||||
request_id: '1eb6473c-8df0-924e-1c8d-e016a6420aee',
|
||||
lease_id: '',
|
||||
renewable: false,
|
||||
lease_duration: 0,
|
||||
data: {
|
||||
keys: null,
|
||||
},
|
||||
wrap_info: null,
|
||||
warnings: null,
|
||||
auth: null,
|
||||
mount_type: 'database',
|
||||
backend: 'database',
|
||||
},
|
||||
dataset: ['connection', 'connection2'],
|
||||
};
|
||||
this.pagination.setLazyCacheForModel(modelName, key, value);
|
||||
const cacheEntry = this.pagination.lazyCaches.get(dasherize(modelName));
|
||||
const actual = Object.fromEntries(cacheEntry); // convert from Map to Object for assertion
|
||||
const expected = { '{"backend":"database","id":""}': value };
|
||||
assert.propEqual(actual, expected, 'model name is dasherized and can be retrieved from lazyCache');
|
||||
});
|
||||
|
||||
test('keyForCache', function (assert) {
|
||||
const query = { id: 1 };
|
||||
const queryWithSize = { id: 1, size: 1 };
|
||||
assert.deepEqual(keyForCache(query), JSON.stringify(query), 'generated the correct cache key');
|
||||
assert.deepEqual(keyForCache(queryWithSize), JSON.stringify(query), 'excludes size from query cache');
|
||||
});
|
||||
|
||||
test('clamp', function (assert) {
|
||||
assert.strictEqual(clamp('foo', 0, 100), 0, 'returns the min if passed a non-number');
|
||||
assert.strictEqual(clamp(0, 1, 100), 1, 'returns the min when passed number is less than the min');
|
||||
assert.strictEqual(clamp(200, 1, 100), 100, 'returns the max passed number is greater than the max');
|
||||
assert.strictEqual(clamp(50, 1, 100), 50, 'returns the passed number when it is in range');
|
||||
});
|
||||
|
||||
test('pagination.storeDataset', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const query = { id: 1 };
|
||||
this.pagination.storeDataset('data', query, {}, arr);
|
||||
|
||||
assert.deepEqual(
|
||||
this.pagination.getDataset('data', query).dataset,
|
||||
arr,
|
||||
'it stores the array as .dataset'
|
||||
);
|
||||
assert.deepEqual(
|
||||
this.pagination.getDataset('data', query).response,
|
||||
{},
|
||||
'it stores the response as .response'
|
||||
);
|
||||
assert.ok(this.pagination.get('lazyCaches').has('data'), 'it stores model map');
|
||||
assert.ok(
|
||||
this.pagination.get('lazyCaches').get('data').has(keyForCache(query)),
|
||||
'it stores data on the model map'
|
||||
);
|
||||
});
|
||||
|
||||
test('pagination.clearDataset with a prefix', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const arr2 = ['one', 'two', 'three', 'four'];
|
||||
this.pagination.storeDataset('data', { id: 1 }, {}, arr);
|
||||
this.pagination.storeDataset('transit-key', { id: 2 }, {}, arr2);
|
||||
assert.strictEqual(this.pagination.get('lazyCaches').size, 2, 'it stores both keys');
|
||||
|
||||
this.pagination.clearDataset('transit-key');
|
||||
assert.strictEqual(this.pagination.get('lazyCaches').size, 1, 'deletes one key');
|
||||
assert.notOk(this.pagination.get('lazyCaches').has('transit-key'), 'cache is no longer stored');
|
||||
});
|
||||
|
||||
test('pagination.clearDataset with no args clears entire cache', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const arr2 = ['one', 'two', 'three', 'four'];
|
||||
this.pagination.storeDataset('data', { id: 1 }, {}, arr);
|
||||
this.pagination.storeDataset('transit-key', { id: 2 }, {}, arr2);
|
||||
assert.strictEqual(this.pagination.get('lazyCaches').size, 2, 'it stores both keys');
|
||||
|
||||
this.pagination.clearDataset();
|
||||
assert.strictEqual(this.pagination.get('lazyCaches').size, 0, 'deletes all of the keys');
|
||||
assert.notOk(this.pagination.get('lazyCaches').has('transit-key'), 'first cache key is no longer stored');
|
||||
assert.notOk(this.pagination.get('lazyCaches').has('data'), 'second cache key is no longer stored');
|
||||
});
|
||||
|
||||
test('pagination.getDataset', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
this.pagination.storeDataset('data', { id: 1 }, {}, arr);
|
||||
|
||||
assert.deepEqual(this.pagination.getDataset('data', { id: 1 }), { response: {}, dataset: arr });
|
||||
});
|
||||
|
||||
test('pagination.constructResponse', function (assert) {
|
||||
const arr = ['one', 'two', 'three', 'fifteen', 'twelve'];
|
||||
this.pagination.storeDataset('data', { id: 1 }, {}, arr);
|
||||
|
||||
assert.deepEqual(
|
||||
this.pagination.constructResponse('data', {
|
||||
id: 1,
|
||||
pageFilter: 't',
|
||||
page: 1,
|
||||
size: 3,
|
||||
responsePath: 'data',
|
||||
}),
|
||||
{
|
||||
data: ['two', 'three', 'fifteen'],
|
||||
meta: {
|
||||
currentPage: 1,
|
||||
lastPage: 2,
|
||||
nextPage: 2,
|
||||
prevPage: 1,
|
||||
total: 5,
|
||||
filteredTotal: 4,
|
||||
pageSize: 3,
|
||||
},
|
||||
},
|
||||
'it returns filtered results'
|
||||
);
|
||||
});
|
||||
|
||||
test('pagination.fetchPage', async function (assert) {
|
||||
const keys = ['zero', 'one', 'two', 'three', 'four', 'five', 'six'];
|
||||
const data = {
|
||||
data: {
|
||||
keys,
|
||||
},
|
||||
};
|
||||
const pageSize = 2;
|
||||
const query = {
|
||||
size: pageSize,
|
||||
page: 1,
|
||||
responsePath: 'data.keys',
|
||||
};
|
||||
this.pagination.storeDataset('transit-key', query, data, keys);
|
||||
|
||||
let result;
|
||||
result = await this.pagination.fetchPage('transit-key', query);
|
||||
assert.strictEqual(result.get('length'), pageSize, 'returns the correct number of items');
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(0, pageSize),
|
||||
'returns the first page of items'
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.get('meta'),
|
||||
{
|
||||
nextPage: 2,
|
||||
prevPage: 1,
|
||||
currentPage: 1,
|
||||
lastPage: 4,
|
||||
total: 7,
|
||||
filteredTotal: 7,
|
||||
pageSize: 2,
|
||||
},
|
||||
'returns correct meta values'
|
||||
);
|
||||
|
||||
result = await this.pagination.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 3,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
const pageThreeEnd = 3 * pageSize;
|
||||
const pageThreeStart = pageThreeEnd - pageSize;
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(pageThreeStart, pageThreeEnd),
|
||||
'returns the third page of items'
|
||||
);
|
||||
|
||||
result = await this.pagination.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 99,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(keys.length - 1),
|
||||
'returns the last page when the page value is beyond the of bounds'
|
||||
);
|
||||
|
||||
result = await this.pagination.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 0,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(0, pageSize),
|
||||
'returns the first page when page value is under the bounds'
|
||||
);
|
||||
});
|
||||
|
||||
test('pagination.lazyPaginatedQuery', async function (assert) {
|
||||
const response = {
|
||||
data: ['foo'],
|
||||
};
|
||||
let queryArgs;
|
||||
const adapterForStub = () => {
|
||||
return {
|
||||
query(store, modelName, query) {
|
||||
queryArgs = query;
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
};
|
||||
};
|
||||
Sinon.stub(this.store, 'adapterFor').callsFake(adapterForStub);
|
||||
// stub fetchPage because we test it separately
|
||||
Sinon.stub(this.pagination, 'fetchPage').callsFake(() => {});
|
||||
const query = { page: 1, size: 1, responsePath: 'data' };
|
||||
|
||||
await this.pagination.lazyPaginatedQuery('transit-key', query);
|
||||
assert.deepEqual(
|
||||
this.pagination.getDataset('transit-key', query),
|
||||
{ response: { data: null }, dataset: ['foo'] },
|
||||
'stores returned dataset'
|
||||
);
|
||||
|
||||
await this.pagination.lazyPaginatedQuery('secret', { page: 1, responsePath: 'data' });
|
||||
assert.strictEqual(queryArgs.size, DEFAULT_PAGE_SIZE, 'calls query with DEFAULT_PAGE_SIZE');
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
this.pagination.lazyPaginatedQuery('transit-key', {});
|
||||
},
|
||||
/responsePath is required/,
|
||||
'requires responsePath'
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
this.pagination.lazyPaginatedQuery('transit-key', { responsePath: 'foo' });
|
||||
},
|
||||
/page is required/,
|
||||
'requires page'
|
||||
);
|
||||
});
|
||||
|
||||
test('pagination.filterData', async function (assert) {
|
||||
const dataset = [
|
||||
{ id: 'foo', name: 'Foo', type: 'test' },
|
||||
{ id: 'bar', name: 'Bar', type: 'test' },
|
||||
{ id: 'bar-2', name: 'Bar', type: null },
|
||||
];
|
||||
|
||||
const defaultFiltering = this.pagination.filterData('foo', dataset);
|
||||
assert.deepEqual(defaultFiltering, [{ id: 'foo', name: 'Foo', type: 'test' }]);
|
||||
|
||||
const filter = (data) => {
|
||||
return data.filter((d) => d.name === 'Bar' && d.type === 'test');
|
||||
};
|
||||
const customFiltering = this.pagination.filterData(filter, dataset);
|
||||
assert.deepEqual(customFiltering, [{ id: 'bar', name: 'Bar', type: 'test' }]);
|
||||
});
|
||||
});
|
||||
@ -1,257 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { resolve } from 'rsvp';
|
||||
import { run } from '@ember/runloop';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { normalizeModelName, keyForCache } from 'vault/services/store';
|
||||
import clamp from 'vault/utils/clamp';
|
||||
import config from 'vault/config/environment';
|
||||
|
||||
const { DEFAULT_PAGE_SIZE } = config.APP;
|
||||
|
||||
module('Unit | Service | store', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.store = this.owner.lookup('service:store');
|
||||
});
|
||||
|
||||
test('normalizeModelName', function (assert) {
|
||||
assert.strictEqual(normalizeModelName('oneThing'), 'one-thing', 'dasherizes modelName');
|
||||
});
|
||||
|
||||
test('keyForCache', function (assert) {
|
||||
const query = { id: 1 };
|
||||
const queryWithSize = { id: 1, size: 1 };
|
||||
assert.deepEqual(keyForCache(query), JSON.stringify(query), 'generated the correct cache key');
|
||||
assert.deepEqual(keyForCache(queryWithSize), JSON.stringify(query), 'excludes size from query cache');
|
||||
});
|
||||
|
||||
test('clamp', function (assert) {
|
||||
assert.strictEqual(clamp('foo', 0, 100), 0, 'returns the min if passed a non-number');
|
||||
assert.strictEqual(clamp(0, 1, 100), 1, 'returns the min when passed number is less than the min');
|
||||
assert.strictEqual(clamp(200, 1, 100), 100, 'returns the max passed number is greater than the max');
|
||||
assert.strictEqual(clamp(50, 1, 100), 50, 'returns the passed number when it is in range');
|
||||
});
|
||||
|
||||
test('store.storeDataset', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const query = { id: 1 };
|
||||
this.store.storeDataset('data', query, {}, arr);
|
||||
|
||||
assert.deepEqual(this.store.getDataset('data', query).dataset, arr, 'it stores the array as .dataset');
|
||||
assert.deepEqual(
|
||||
this.store.getDataset('data', query).response,
|
||||
{},
|
||||
'it stores the response as .response'
|
||||
);
|
||||
assert.ok(this.store.get('lazyCaches').has('data'), 'it stores model map');
|
||||
assert.ok(
|
||||
this.store.get('lazyCaches').get('data').has(keyForCache(query)),
|
||||
'it stores data on the model map'
|
||||
);
|
||||
});
|
||||
|
||||
test('store.clearDataset with a prefix', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const arr2 = ['one', 'two', 'three', 'four'];
|
||||
this.store.storeDataset('data', { id: 1 }, {}, arr);
|
||||
this.store.storeDataset('transit-key', { id: 2 }, {}, arr2);
|
||||
assert.strictEqual(this.store.get('lazyCaches').size, 2, 'it stores both keys');
|
||||
|
||||
this.store.clearDataset('transit-key');
|
||||
assert.strictEqual(this.store.get('lazyCaches').size, 1, 'deletes one key');
|
||||
assert.notOk(this.store.get('lazyCaches').has('transit-key'), 'cache is no longer stored');
|
||||
});
|
||||
|
||||
test('store.clearDataset with no args clears entire cache', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
const arr2 = ['one', 'two', 'three', 'four'];
|
||||
this.store.storeDataset('data', { id: 1 }, {}, arr);
|
||||
this.store.storeDataset('transit-key', { id: 2 }, {}, arr2);
|
||||
assert.strictEqual(this.store.get('lazyCaches').size, 2, 'it stores both keys');
|
||||
|
||||
this.store.clearDataset();
|
||||
assert.strictEqual(this.store.get('lazyCaches').size, 0, 'deletes all of the keys');
|
||||
assert.notOk(this.store.get('lazyCaches').has('transit-key'), 'first cache key is no longer stored');
|
||||
assert.notOk(this.store.get('lazyCaches').has('data'), 'second cache key is no longer stored');
|
||||
});
|
||||
|
||||
test('store.getDataset', function (assert) {
|
||||
const arr = ['one', 'two'];
|
||||
this.store.storeDataset('data', { id: 1 }, {}, arr);
|
||||
|
||||
assert.deepEqual(this.store.getDataset('data', { id: 1 }), { response: {}, dataset: arr });
|
||||
});
|
||||
|
||||
test('store.constructResponse', function (assert) {
|
||||
const arr = ['one', 'two', 'three', 'fifteen', 'twelve'];
|
||||
this.store.storeDataset('data', { id: 1 }, {}, arr);
|
||||
|
||||
assert.deepEqual(
|
||||
this.store.constructResponse('data', {
|
||||
id: 1,
|
||||
pageFilter: 't',
|
||||
page: 1,
|
||||
size: 3,
|
||||
responsePath: 'data',
|
||||
}),
|
||||
{
|
||||
data: ['two', 'three', 'fifteen'],
|
||||
meta: {
|
||||
currentPage: 1,
|
||||
lastPage: 2,
|
||||
nextPage: 2,
|
||||
prevPage: 1,
|
||||
total: 5,
|
||||
filteredTotal: 4,
|
||||
pageSize: 3,
|
||||
},
|
||||
},
|
||||
'it returns filtered results'
|
||||
);
|
||||
});
|
||||
|
||||
test('store.fetchPage', async function (assert) {
|
||||
const keys = ['zero', 'one', 'two', 'three', 'four', 'five', 'six'];
|
||||
const data = {
|
||||
data: {
|
||||
keys,
|
||||
},
|
||||
};
|
||||
const pageSize = 2;
|
||||
const query = {
|
||||
size: pageSize,
|
||||
page: 1,
|
||||
responsePath: 'data.keys',
|
||||
};
|
||||
this.store.storeDataset('transit-key', query, data, keys);
|
||||
|
||||
let result;
|
||||
result = await this.store.fetchPage('transit-key', query);
|
||||
assert.strictEqual(result.get('length'), pageSize, 'returns the correct number of items');
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(0, pageSize),
|
||||
'returns the first page of items'
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.get('meta'),
|
||||
{
|
||||
nextPage: 2,
|
||||
prevPage: 1,
|
||||
currentPage: 1,
|
||||
lastPage: 4,
|
||||
total: 7,
|
||||
filteredTotal: 7,
|
||||
pageSize: 2,
|
||||
},
|
||||
'returns correct meta values'
|
||||
);
|
||||
|
||||
result = await this.store.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 3,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
const pageThreeEnd = 3 * pageSize;
|
||||
const pageThreeStart = pageThreeEnd - pageSize;
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(pageThreeStart, pageThreeEnd),
|
||||
'returns the third page of items'
|
||||
);
|
||||
|
||||
result = await this.store.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 99,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(keys.length - 1),
|
||||
'returns the last page when the page value is beyond the of bounds'
|
||||
);
|
||||
|
||||
result = await this.store.fetchPage('transit-key', {
|
||||
size: pageSize,
|
||||
page: 0,
|
||||
responsePath: 'data.keys',
|
||||
});
|
||||
assert.deepEqual(
|
||||
result.map((r) => r.id),
|
||||
keys.slice(0, pageSize),
|
||||
'returns the first page when page value is under the bounds'
|
||||
);
|
||||
});
|
||||
|
||||
test('store.lazyPaginatedQuery', function (assert) {
|
||||
const response = {
|
||||
data: ['foo'],
|
||||
};
|
||||
let queryArgs;
|
||||
const store = this.owner.factoryFor('service:store').create({
|
||||
adapterFor() {
|
||||
return {
|
||||
query(store, modelName, query) {
|
||||
queryArgs = query;
|
||||
return resolve(response);
|
||||
},
|
||||
};
|
||||
},
|
||||
fetchPage() {},
|
||||
});
|
||||
|
||||
const query = { page: 1, size: 1, responsePath: 'data' };
|
||||
run(function () {
|
||||
store.lazyPaginatedQuery('transit-key', query);
|
||||
});
|
||||
assert.deepEqual(
|
||||
store.getDataset('transit-key', query),
|
||||
{ response: { data: null }, dataset: ['foo'] },
|
||||
'stores returned dataset'
|
||||
);
|
||||
|
||||
run(function () {
|
||||
store.lazyPaginatedQuery('secret', { page: 1, responsePath: 'data' });
|
||||
});
|
||||
assert.strictEqual(queryArgs.size, DEFAULT_PAGE_SIZE, 'calls query with DEFAULT_PAGE_SIZE');
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
store.lazyPaginatedQuery('transit-key', {});
|
||||
},
|
||||
/responsePath is required/,
|
||||
'requires responsePath'
|
||||
);
|
||||
assert.throws(
|
||||
() => {
|
||||
store.lazyPaginatedQuery('transit-key', { responsePath: 'foo' });
|
||||
},
|
||||
/page is required/,
|
||||
'requires page'
|
||||
);
|
||||
});
|
||||
|
||||
test('store.filterData', async function (assert) {
|
||||
const dataset = [
|
||||
{ id: 'foo', name: 'Foo', type: 'test' },
|
||||
{ id: 'bar', name: 'Bar', type: 'test' },
|
||||
{ id: 'bar-2', name: 'Bar', type: null },
|
||||
];
|
||||
|
||||
const defaultFiltering = this.store.filterData('foo', dataset);
|
||||
assert.deepEqual(defaultFiltering, [{ id: 'foo', name: 'Foo', type: 'test' }]);
|
||||
|
||||
const filter = (data) => {
|
||||
return data.filter((d) => d.name === 'Bar' && d.type === 'test');
|
||||
};
|
||||
const customFiltering = this.store.filterData(filter, dataset);
|
||||
assert.deepEqual(customFiltering, [{ id: 'bar', name: 'Bar', type: 'test' }]);
|
||||
});
|
||||
});
|
||||
16
ui/types/vault/services/pagination.d.ts
vendored
Normal file
16
ui/types/vault/services/pagination.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Service from '@ember/service';
|
||||
import { RecordArray } from '@ember-data/store';
|
||||
|
||||
export default class PaginationService extends Service {
|
||||
lazyPaginatedQuery(
|
||||
modelName: string,
|
||||
query: object,
|
||||
options?: { adapterOptions: object }
|
||||
): Promise<RecordArray>;
|
||||
clearDataset(modelName: string);
|
||||
}
|
||||
11
ui/types/vault/services/store.d.ts
vendored
11
ui/types/vault/services/store.d.ts
vendored
@ -3,16 +3,11 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Store, { RecordArray } from '@ember-data/store';
|
||||
import Store from '@ember-data/store';
|
||||
|
||||
export default class StoreService extends Store {
|
||||
lazyPaginatedQuery(
|
||||
modelName: string,
|
||||
query: object,
|
||||
options?: { adapterOptions: object }
|
||||
): Promise<RecordArray>;
|
||||
|
||||
clearDataset(modelName: string);
|
||||
adapterFor(modelName: string);
|
||||
createRecord(modelName: string, object);
|
||||
findRecord(modelName: string, path: string);
|
||||
peekRecord(modelName: string, path: string);
|
||||
query(modelName: string, query: object);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user