[core.savedObjects] Add helper for using find with pit and search_after. (#92981)

This commit is contained in:
Luke Elmers 2021-03-24 17:59:13 -06:00 committed by GitHub
parent 9d472bceaf
commit 49078c82bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 905 additions and 168 deletions

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) &gt; [close](./kibana-plugin-core-server.isavedobjectspointintimefinder.close.md)
## ISavedObjectsPointInTimeFinder.close property
Closes the Point-In-Time associated with this finder instance.
Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason.
<b>Signature:</b>
```typescript
close: () => Promise<void>;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) &gt; [find](./kibana-plugin-core-server.isavedobjectspointintimefinder.find.md)
## ISavedObjectsPointInTimeFinder.find property
An async generator which wraps calls to `savedObjectsClient.find` and iterates over multiple pages of results using `_pit` and `search_after`<!-- -->. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage` size.
<b>Signature:</b>
```typescript
find: () => AsyncGenerator<SavedObjectsFindResponse>;
```

View file

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md)
## ISavedObjectsPointInTimeFinder interface
<b>Signature:</b>
```typescript
export interface ISavedObjectsPointInTimeFinder
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [close](./kibana-plugin-core-server.isavedobjectspointintimefinder.close.md) | <code>() =&gt; Promise&lt;void&gt;</code> | Closes the Point-In-Time associated with this finder instance.<!-- -->Once you have retrieved all of the results you need, it is recommended to call <code>close()</code> to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to <code>find</code> fails for any reason. |
| [find](./kibana-plugin-core-server.isavedobjectspointintimefinder.find.md) | <code>() =&gt; AsyncGenerator&lt;SavedObjectsFindResponse&gt;</code> | An async generator which wraps calls to <code>savedObjectsClient.find</code> and iterates over multiple pages of results using <code>_pit</code> and <code>search_after</code>. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated <code>perPage</code> size. |

View file

@ -98,6 +98,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [IndexSettingsDeprecationInfo](./kibana-plugin-core-server.indexsettingsdeprecationinfo.md) | |
| [IRenderOptions](./kibana-plugin-core-server.irenderoptions.md) | |
| [IRouter](./kibana-plugin-core-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-core-server.routeconfig.md) and [RequestHandler](./kibana-plugin-core-server.requesthandler.md) for more information about arguments to route registrations. |
| [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | |
| [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) | Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional <code>asCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>asInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. |
| [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. |
| [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. |
@ -158,6 +159,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [SavedObjectsComplexFieldMapping](./kibana-plugin-core-server.savedobjectscomplexfieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) for documentation. |
| [SavedObjectsCoreFieldMapping](./kibana-plugin-core-server.savedobjectscorefieldmapping.md) | See [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) for documentation. |
| [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) | |
| [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md) | |
| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-core-server.savedobjectsdeletebynamespaceoptions.md) | |
| [SavedObjectsDeleteFromNamespacesOptions](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesoptions.md) | |
| [SavedObjectsDeleteFromNamespacesResponse](./kibana-plugin-core-server.savedobjectsdeletefromnamespacesresponse.md) | |
@ -305,6 +307,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [SavedObjectsClientFactoryProvider](./kibana-plugin-core-server.savedobjectsclientfactoryprovider.md) | Provider to invoke to retrieve a [SavedObjectsClientFactory](./kibana-plugin-core-server.savedobjectsclientfactory.md)<!-- -->. |
| [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. |
| [SavedObjectsClosePointInTimeOptions](./kibana-plugin-core-server.savedobjectsclosepointintimeoptions.md) | |
| [SavedObjectsCreatePointInTimeFinderOptions](./kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md) | |
| [SavedObjectsExportTransform](./kibana-plugin-core-server.savedobjectsexporttransform.md) | Transformation function used to mutate the exported objects of the associated type.<!-- -->A type's export transform function will be executed once per user-initiated export, for all objects of that type. |
| [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.<!-- -->Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation |
| [SavedObjectsImportHook](./kibana-plugin-core-server.savedobjectsimporthook.md) | A hook associated with a specific saved object type, that will be invoked during the import process. The hook will have access to the objects of the registered type.<!-- -->Currently, the only supported feature for import hooks is to return warnings to be displayed in the UI when the import succeeds. The only interactions the hook can have with the import process is via the hook's response. Mutating the objects inside the hook's code will have no effect. |

View file

@ -6,6 +6,8 @@
Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md)<!-- -->.
Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method.
<b>Signature:</b>
```typescript

View file

@ -0,0 +1,53 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) &gt; [createPointInTimeFinder](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md)
## SavedObjectsClient.createPointInTimeFinder() method
Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any `find` queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.
Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.
The generator wraps calls to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) and iterates over multiple pages of results using `_pit` and `search_after`<!-- -->. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage`<!-- -->.
Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason.
<b>Signature:</b>
```typescript
createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| findOptions | <code>SavedObjectsCreatePointInTimeFinderOptions</code> | |
| dependencies | <code>SavedObjectsCreatePointInTimeFinderDependencies</code> | |
<b>Returns:</b>
`ISavedObjectsPointInTimeFinder`
## Example
```ts
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: 'visualization',
search: 'foo*',
perPage: 100,
};
const finder = savedObjectsClient.createPointInTimeFinder(findOptions);
const responses: SavedObjectFindResponse[] = [];
for await (const response of finder.find()) {
responses.push(...response);
if (doneSearching) {
await finder.close();
}
}
```

View file

@ -30,13 +30,14 @@ The constructor for this class is marked as internal. Third-party code should no
| [bulkGet(objects, options)](./kibana-plugin-core-server.savedobjectsclient.bulkget.md) | | Returns an array of objects by id |
| [bulkUpdate(objects, options)](./kibana-plugin-core-server.savedobjectsclient.bulkupdate.md) | | Bulk Updates multiple SavedObject at once |
| [checkConflicts(objects, options)](./kibana-plugin-core-server.savedobjectsclient.checkconflicts.md) | | Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. |
| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsclient.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md)<!-- -->. |
| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsclient.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using [SavedObjectsClient.openPointInTimeForType()](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md)<!-- -->.<!-- -->Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. |
| [create(type, attributes, options)](./kibana-plugin-core-server.savedobjectsclient.create.md) | | Persists a SavedObject |
| [createPointInTimeFinder(findOptions, dependencies)](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) | | Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any <code>find</code> queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.<!-- -->Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.<!-- -->The generator wraps calls to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) and iterates over multiple pages of results using <code>_pit</code> and <code>search_after</code>. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated <code>perPage</code>.<!-- -->Once you have retrieved all of the results you need, it is recommended to call <code>close()</code> to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to <code>find</code> fails for any reason. |
| [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.delete.md) | | Deletes a SavedObject |
| [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsclient.deletefromnamespaces.md) | | Removes namespaces from a SavedObject |
| [find(options)](./kibana-plugin-core-server.savedobjectsclient.find.md) | | Find all SavedObjects matching the search query |
| [get(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.get.md) | | Retrieves a single object |
| [openPointInTimeForType(type, options)](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned <code>id</code> can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT. |
| [openPointInTimeForType(type, options)](./kibana-plugin-core-server.savedobjectsclient.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned <code>id</code> can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT.<!-- -->Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method. |
| [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.removereferencesto.md) | | Updates all objects containing a reference to the given {<!-- -->type, id<!-- -->} tuple to remove the said reference. |
| [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsclient.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists |
| [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsclient.update.md) | | Updates an SavedObject |

View file

@ -6,6 +6,8 @@
Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned `id` can then be passed to [SavedObjectsClient.find()](./kibana-plugin-core-server.savedobjectsclient.find.md) to search against that PIT.
Only use this API if you have an advanced use case that's not solved by the [SavedObjectsClient.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsclient.createpointintimefinder.md) method.
<b>Signature:</b>
```typescript

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md) &gt; [client](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md)
## SavedObjectsCreatePointInTimeFinderDependencies.client property
<b>Signature:</b>
```typescript
client: Pick<SavedObjectsClientContract, 'find' | 'openPointInTimeForType' | 'closePointInTime'>;
```

View file

@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsCreatePointInTimeFinderDependencies](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.md)
## SavedObjectsCreatePointInTimeFinderDependencies interface
<b>Signature:</b>
```typescript
export interface SavedObjectsCreatePointInTimeFinderDependencies
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [client](./kibana-plugin-core-server.savedobjectscreatepointintimefinderdependencies.client.md) | <code>Pick&lt;SavedObjectsClientContract, 'find' &#124; 'openPointInTimeForType' &#124; 'closePointInTime'&gt;</code> | |

View file

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsCreatePointInTimeFinderOptions](./kibana-plugin-core-server.savedobjectscreatepointintimefinderoptions.md)
## SavedObjectsCreatePointInTimeFinderOptions type
<b>Signature:</b>
```typescript
export declare type SavedObjectsCreatePointInTimeFinderOptions = Omit<SavedObjectsFindOptions, 'page' | 'pit' | 'searchAfter'>;
```

View file

@ -6,6 +6,8 @@
Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using `openPointInTimeForType`<!-- -->.
Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method.
<b>Signature:</b>
```typescript

View file

@ -0,0 +1,53 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) &gt; [createPointInTimeFinder](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md)
## SavedObjectsRepository.createPointInTimeFinder() method
Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any `find` queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.
Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.
This generator wraps calls to [SavedObjectsRepository.find()](./kibana-plugin-core-server.savedobjectsrepository.find.md) and iterates over multiple pages of results using `_pit` and `search_after`<!-- -->. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated `perPage`<!-- -->.
Once you have retrieved all of the results you need, it is recommended to call `close()` to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to `find` fails for any reason.
<b>Signature:</b>
```typescript
createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| findOptions | <code>SavedObjectsCreatePointInTimeFinderOptions</code> | |
| dependencies | <code>SavedObjectsCreatePointInTimeFinderDependencies</code> | |
<b>Returns:</b>
`ISavedObjectsPointInTimeFinder`
## Example
```ts
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: 'visualization',
search: 'foo*',
perPage: 100,
};
const finder = savedObjectsClient.createPointInTimeFinder(findOptions);
const responses: SavedObjectFindResponse[] = [];
for await (const response of finder.find()) {
responses.push(...response);
if (doneSearching) {
await finder.close();
}
}
```

View file

@ -20,15 +20,16 @@ export declare class SavedObjectsRepository
| [bulkGet(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id |
| [bulkUpdate(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk |
| [checkConflicts(objects, options)](./kibana-plugin-core-server.savedobjectsrepository.checkconflicts.md) | | Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. |
| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsrepository.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using <code>openPointInTimeForType</code>. |
| [closePointInTime(id, options)](./kibana-plugin-core-server.savedobjectsrepository.closepointintime.md) | | Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the Elasticsearch client, and is included in the Saved Objects Client as a convenience for consumers who are using <code>openPointInTimeForType</code>.<!-- -->Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. |
| [create(type, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.create.md) | | Persists an object |
| [createPointInTimeFinder(findOptions, dependencies)](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) | | Returns a [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) to help page through large sets of saved objects. We strongly recommend using this API for any <code>find</code> queries that might return more than 1000 saved objects, however this API is only intended for use in server-side "batch" processing of objects where you are collecting all objects in memory or streaming them back to the client.<!-- -->Do NOT use this API in a route handler to facilitate paging through saved objects on the client-side unless you are streaming all of the results back to the client at once. Because the returned generator is stateful, you cannot rely on subsequent http requests retrieving new pages from the same Kibana server in multi-instance deployments.<!-- -->This generator wraps calls to [SavedObjectsRepository.find()](./kibana-plugin-core-server.savedobjectsrepository.find.md) and iterates over multiple pages of results using <code>_pit</code> and <code>search_after</code>. This will open a new Point-In-Time (PIT), and continue paging until a set of results is received that's smaller than the designated <code>perPage</code>.<!-- -->Once you have retrieved all of the results you need, it is recommended to call <code>close()</code> to clean up the PIT and prevent Elasticsearch from consuming resources unnecessarily. This is only required if you are done iterating and have not yet paged through all of the results: the PIT will automatically be closed for you once you reach the last page of results, or if the underlying call to <code>find</code> fails for any reason. |
| [delete(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.delete.md) | | Deletes an object |
| [deleteByNamespace(namespace, options)](./kibana-plugin-core-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. |
| [deleteFromNamespaces(type, id, namespaces, options)](./kibana-plugin-core-server.savedobjectsrepository.deletefromnamespaces.md) | | Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted entirely. This method and \[<code>addToNamespaces</code>\][SavedObjectsRepository.addToNamespaces()](./kibana-plugin-core-server.savedobjectsrepository.addtonamespaces.md) are the only ways to change which Spaces a multi-namespace saved object is shared to. |
| [find(options)](./kibana-plugin-core-server.savedobjectsrepository.find.md) | | |
| [get(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.get.md) | | Gets a single object |
| [incrementCounter(type, id, counterFields, options)](./kibana-plugin-core-server.savedobjectsrepository.incrementcounter.md) | | Increments all the specified counter fields (by one by default). Creates the document if one doesn't exist for the given id. |
| [openPointInTimeForType(type, { keepAlive, preference })](./kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned <code>id</code> can then be passed to <code>SavedObjects.find</code> to search against that PIT. |
| [openPointInTimeForType(type, { keepAlive, preference })](./kibana-plugin-core-server.savedobjectsrepository.openpointintimefortype.md) | | Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned <code>id</code> can then be passed to <code>SavedObjects.find</code> to search against that PIT.<!-- -->Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method. |
| [removeReferencesTo(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.removereferencesto.md) | | Updates all objects containing a reference to the given {<!-- -->type, id<!-- -->} tuple to remove the said reference. |
| [resolve(type, id, options)](./kibana-plugin-core-server.savedobjectsrepository.resolve.md) | | Resolves a single object, using any legacy URL alias if it exists |
| [update(type, id, attributes, options)](./kibana-plugin-core-server.savedobjectsrepository.update.md) | | Updates an object |

View file

@ -6,6 +6,8 @@
Opens a Point In Time (PIT) against the indices for the specified Saved Object types. The returned `id` can then be passed to `SavedObjects.find` to search against that PIT.
Only use this API if you have an advanced use case that's not solved by the [SavedObjectsRepository.createPointInTimeFinder()](./kibana-plugin-core-server.savedobjectsrepository.createpointintimefinder.md) method.
<b>Signature:</b>
```typescript

View file

@ -12,7 +12,7 @@ start(core: CoreStart): {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "closePointInTime" | "create" | "update" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "create" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "update" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "closePointInTime" | "createPointInTimeFinder" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};
@ -31,7 +31,7 @@ start(core: CoreStart): {
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "closePointInTime" | "create" | "update" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "create" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "update" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "closePointInTime" | "createPointInTimeFinder" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
}`

View file

@ -282,6 +282,9 @@ export type {
SavedObjectsClientFactoryProvider,
SavedObjectsClosePointInTimeOptions,
SavedObjectsClosePointInTimeResponse,
ISavedObjectsPointInTimeFinder,
SavedObjectsCreatePointInTimeFinderDependencies,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsCreateOptions,
SavedObjectsExportResultDetails,
SavedObjectsFindResult,

View file

@ -9,7 +9,7 @@
import { createListStream } from '@kbn/utils';
import { PublicMethodsOf } from '@kbn/utility-types';
import { Logger } from '../../logging';
import { SavedObject, SavedObjectsClientContract, SavedObjectsFindOptions } from '../types';
import { SavedObject, SavedObjectsClientContract } from '../types';
import { SavedObjectsFindResult } from '../service';
import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry';
import { fetchNestedDependencies } from './fetch_nested_dependencies';
@ -23,7 +23,6 @@ import {
} from './types';
import { SavedObjectsExportError } from './errors';
import { applyExportTransforms } from './apply_export_transforms';
import { createPointInTimeFinder } from './point_in_time_finder';
import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils';
/**
@ -168,18 +167,12 @@ export class SavedObjectsExporter {
hasReference,
search,
}: SavedObjectsExportByTypeOptions) {
const findOptions: SavedObjectsFindOptions = {
const finder = this.#savedObjectsClient.createPointInTimeFinder({
type: types,
hasReference,
hasReferenceOperator: hasReference ? 'OR' : undefined,
search,
namespaces: namespace ? [namespace] : undefined,
};
const finder = createPointInTimeFinder({
findOptions,
logger: this.#log,
savedObjectsClient: this.#savedObjectsClient,
});
const hits: SavedObjectsFindResult[] = [];

View file

@ -274,7 +274,7 @@ describe('SavedObjectsService', () => {
expect(coreStart.elasticsearch.client.asScoped).toHaveBeenCalledWith(req);
const [
[, , , , includedHiddenTypes],
[, , , , , includedHiddenTypes],
] = (SavedObjectsRepository.createRepository as jest.Mocked<any>).mock.calls;
expect(includedHiddenTypes).toEqual([]);
@ -292,7 +292,7 @@ describe('SavedObjectsService', () => {
createScopedRepository(req, ['someHiddenType']);
const [
[, , , , includedHiddenTypes],
[, , , , , includedHiddenTypes],
] = (SavedObjectsRepository.createRepository as jest.Mocked<any>).mock.calls;
expect(includedHiddenTypes).toEqual(['someHiddenType']);
@ -311,7 +311,7 @@ describe('SavedObjectsService', () => {
createInternalRepository();
const [
[, , , client, includedHiddenTypes],
[, , , client, , includedHiddenTypes],
] = (SavedObjectsRepository.createRepository as jest.Mocked<any>).mock.calls;
expect(coreStart.elasticsearch.client.asInternalUser).toBe(client);
@ -328,7 +328,7 @@ describe('SavedObjectsService', () => {
createInternalRepository(['someHiddenType']);
const [
[, , , , includedHiddenTypes],
[, , , , , includedHiddenTypes],
] = (SavedObjectsRepository.createRepository as jest.Mocked<any>).mock.calls;
expect(includedHiddenTypes).toEqual(['someHiddenType']);

View file

@ -421,6 +421,7 @@ export class SavedObjectsService
this.typeRegistry,
kibanaConfig.index,
esClient,
this.logger.get('repository'),
includedHiddenTypes
);
};

View file

@ -8,6 +8,9 @@
export { SavedObjectsErrorHelpers, SavedObjectsClientProvider, SavedObjectsUtils } from './lib';
export type {
SavedObjectsRepository,
ISavedObjectsPointInTimeFinder,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
ISavedObjectsClientProvider,
SavedObjectsClientProviderOptions,
SavedObjectsClientWrapperFactory,

View file

@ -8,6 +8,13 @@
export type { ISavedObjectsRepository, SavedObjectsRepository } from './repository';
export { SavedObjectsClientProvider } from './scoped_client_provider';
export type {
ISavedObjectsPointInTimeFinder,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
} from './point_in_time_finder';
export type {
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { loggerMock, MockedLogger } from '../../../logging/logger.mock';
import type { SavedObjectsClientContract } from '../../types';
import type { ISavedObjectsRepository } from './repository';
import { PointInTimeFinder } from './point_in_time_finder';
const createPointInTimeFinderMock = ({
logger = loggerMock.create(),
savedObjectsMock,
}: {
logger?: MockedLogger;
savedObjectsMock: jest.Mocked<ISavedObjectsRepository | SavedObjectsClientContract>;
}): jest.Mock => {
const mock = jest.fn();
// To simplify testing, we use the actual implementation here, but pass through the
// mocked dependencies. This allows users to set their own `mockResolvedValue` on
// the SO client mock and have it reflected when using `createPointInTimeFinder`.
mock.mockImplementation((findOptions) => {
const finder = new PointInTimeFinder(findOptions, {
logger,
client: savedObjectsMock,
});
jest.spyOn(finder, 'find');
jest.spyOn(finder, 'close');
return finder;
});
return mock;
};
export const savedObjectsPointInTimeFinderMock = {
create: createPointInTimeFinderMock,
};

View file

@ -6,12 +6,15 @@
* Side Public License, v 1.
*/
import { savedObjectsClientMock } from '../service/saved_objects_client.mock';
import { loggerMock, MockedLogger } from '../../logging/logger.mock';
import { SavedObjectsFindOptions } from '../types';
import { SavedObjectsFindResult } from '../service';
import { loggerMock, MockedLogger } from '../../../logging/logger.mock';
import type { SavedObjectsClientContract } from '../../types';
import type { SavedObjectsFindResult } from '../';
import { savedObjectsRepositoryMock } from './repository.mock';
import { createPointInTimeFinder } from './point_in_time_finder';
import {
PointInTimeFinder,
SavedObjectsCreatePointInTimeFinderOptions,
} from './point_in_time_finder';
const mockHits = [
{
@ -40,26 +43,31 @@ const mockHits = [
describe('createPointInTimeFinder()', () => {
let logger: MockedLogger;
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
let find: jest.Mocked<SavedObjectsClientContract>['find'];
let openPointInTimeForType: jest.Mocked<SavedObjectsClientContract>['openPointInTimeForType'];
let closePointInTime: jest.Mocked<SavedObjectsClientContract>['closePointInTime'];
beforeEach(() => {
logger = loggerMock.create();
savedObjectsClient = savedObjectsClientMock.create();
const mockRepository = savedObjectsRepositoryMock.create();
find = mockRepository.find;
openPointInTimeForType = mockRepository.openPointInTimeForType;
closePointInTime = mockRepository.closePointInTime;
});
describe('#find', () => {
test('throws if a PIT is already open', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'abc123',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: mockHits,
pit_id: 'abc123',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: mockHits,
pit_id: 'abc123',
@ -67,31 +75,38 @@ describe('createPointInTimeFinder()', () => {
page: 1,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 1,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
await finder.find().next();
expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
savedObjectsClient.find.mockClear();
expect(find).toHaveBeenCalledTimes(1);
find.mockClear();
expect(async () => {
await finder.find().next();
}).rejects.toThrowErrorMatchingInlineSnapshot(
`"Point In Time has already been opened for this finder instance. Please call \`close()\` before calling \`find()\` again."`
);
expect(savedObjectsClient.find).toHaveBeenCalledTimes(0);
expect(find).toHaveBeenCalledTimes(0);
});
test('works with a single page of results', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'abc123',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: mockHits,
pit_id: 'abc123',
@ -99,22 +114,29 @@ describe('createPointInTimeFinder()', () => {
page: 0,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const hits: SavedObjectsFindResult[] = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
}
expect(hits.length).toBe(2);
expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(1);
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1);
expect(savedObjectsClient.find).toHaveBeenCalledTimes(1);
expect(savedObjectsClient.find).toHaveBeenCalledWith(
expect(openPointInTimeForType).toHaveBeenCalledTimes(1);
expect(closePointInTime).toHaveBeenCalledTimes(1);
expect(find).toHaveBeenCalledTimes(1);
expect(find).toHaveBeenCalledWith(
expect.objectContaining({
pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }),
sortField: 'updated_at',
@ -125,24 +147,24 @@ describe('createPointInTimeFinder()', () => {
});
test('works with multiple pages of results', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'abc123',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [mockHits[0]],
pit_id: 'abc123',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [mockHits[1]],
pit_id: 'abc123',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [],
per_page: 1,
@ -150,25 +172,32 @@ describe('createPointInTimeFinder()', () => {
page: 0,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 1,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const hits: SavedObjectsFindResult[] = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
}
expect(hits.length).toBe(2);
expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(1);
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1);
expect(openPointInTimeForType).toHaveBeenCalledTimes(1);
expect(closePointInTime).toHaveBeenCalledTimes(1);
// called 3 times since we need a 3rd request to check if we
// are done paginating through results.
expect(savedObjectsClient.find).toHaveBeenCalledTimes(3);
expect(savedObjectsClient.find).toHaveBeenCalledWith(
expect(find).toHaveBeenCalledTimes(3);
expect(find).toHaveBeenCalledWith(
expect.objectContaining({
pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }),
sortField: 'updated_at',
@ -181,10 +210,10 @@ describe('createPointInTimeFinder()', () => {
describe('#close', () => {
test('calls closePointInTime with correct ID', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'test',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 1,
saved_objects: [mockHits[0]],
pit_id: 'test',
@ -192,41 +221,48 @@ describe('createPointInTimeFinder()', () => {
page: 0,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 2,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const hits: SavedObjectsFindResult[] = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
await finder.close();
}
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledWith('test');
expect(closePointInTime).toHaveBeenCalledWith('test');
});
test('causes generator to stop', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'test',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [mockHits[0]],
pit_id: 'test',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [mockHits[1]],
pit_id: 'test',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: [],
per_page: 1,
@ -234,36 +270,50 @@ describe('createPointInTimeFinder()', () => {
page: 0,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 1,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const hits: SavedObjectsFindResult[] = [];
for await (const result of finder.find()) {
hits.push(...result.saved_objects);
await finder.close();
}
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(1);
expect(closePointInTime).toHaveBeenCalledTimes(1);
expect(hits.length).toBe(1);
});
test('is called if `find` throws an error', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'test',
});
savedObjectsClient.find.mockRejectedValueOnce(new Error('oops'));
find.mockRejectedValueOnce(new Error('oops'));
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 2,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const hits: SavedObjectsFindResult[] = [];
try {
for await (const result of finder.find()) {
@ -273,21 +323,21 @@ describe('createPointInTimeFinder()', () => {
// intentionally empty
}
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledWith('test');
expect(closePointInTime).toHaveBeenCalledWith('test');
});
test('finder can be reused after closing', async () => {
savedObjectsClient.openPointInTimeForType.mockResolvedValueOnce({
openPointInTimeForType.mockResolvedValueOnce({
id: 'abc123',
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: mockHits,
pit_id: 'abc123',
per_page: 1,
page: 0,
});
savedObjectsClient.find.mockResolvedValueOnce({
find.mockResolvedValueOnce({
total: 2,
saved_objects: mockHits,
pit_id: 'abc123',
@ -295,13 +345,20 @@ describe('createPointInTimeFinder()', () => {
page: 1,
});
const findOptions: SavedObjectsFindOptions = {
const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
type: ['visualization'],
search: 'foo*',
perPage: 1,
};
const finder = createPointInTimeFinder({ findOptions, logger, savedObjectsClient });
const finder = new PointInTimeFinder(findOptions, {
logger,
client: {
find,
openPointInTimeForType,
closePointInTime,
},
});
const findA = finder.find();
await findA.next();
@ -313,9 +370,9 @@ describe('createPointInTimeFinder()', () => {
expect((await findA.next()).done).toBe(true);
expect((await findB.next()).done).toBe(true);
expect(savedObjectsClient.openPointInTimeForType).toHaveBeenCalledTimes(2);
expect(savedObjectsClient.find).toHaveBeenCalledTimes(2);
expect(savedObjectsClient.closePointInTime).toHaveBeenCalledTimes(2);
expect(openPointInTimeForType).toHaveBeenCalledTimes(2);
expect(find).toHaveBeenCalledTimes(2);
expect(closePointInTime).toHaveBeenCalledTimes(2);
});
});
});

View file

@ -6,79 +6,76 @@
* Side Public License, v 1.
*/
import { Logger } from '../../logging';
import { SavedObjectsClientContract, SavedObjectsFindOptions } from '../types';
import { SavedObjectsFindResponse } from '../service';
import type { Logger } from '../../../logging';
import type { SavedObjectsFindOptions, SavedObjectsClientContract } from '../../types';
import type { SavedObjectsFindResponse } from '../';
type PointInTimeFinderClient = Pick<
SavedObjectsClientContract,
'find' | 'openPointInTimeForType' | 'closePointInTime'
>;
/**
* Returns a generator to help page through large sets of saved objects.
*
* The generator wraps calls to `SavedObjects.find` and iterates over
* multiple pages of results using `_pit` and `search_after`. This will
* open a new Point In Time (PIT), and continue paging until a set of
* results is received that's smaller than the designated `perPage`.
*
* Once you have retrieved all of the results you need, it is recommended
* to call `close()` to clean up the PIT and prevent Elasticsearch from
* consuming resources unnecessarily. This will automatically be done for
* you if you reach the last page of results.
*
* @example
* ```ts
* const findOptions: SavedObjectsFindOptions = {
* type: 'visualization',
* search: 'foo*',
* perPage: 100,
* };
*
* const finder = createPointInTimeFinder({
* logger,
* savedObjectsClient,
* findOptions,
* });
*
* const responses: SavedObjectFindResponse[] = [];
* for await (const response of finder.find()) {
* responses.push(...response);
* if (doneSearching) {
* await finder.close();
* }
* }
* ```
* @public
*/
export function createPointInTimeFinder({
findOptions,
logger,
savedObjectsClient,
}: {
findOptions: SavedObjectsFindOptions;
logger: Logger;
savedObjectsClient: SavedObjectsClientContract;
}) {
return new PointInTimeFinder({ findOptions, logger, savedObjectsClient });
export type SavedObjectsCreatePointInTimeFinderOptions = Omit<
SavedObjectsFindOptions,
'page' | 'pit' | 'searchAfter'
>;
/**
* @public
*/
export interface SavedObjectsCreatePointInTimeFinderDependencies {
client: Pick<SavedObjectsClientContract, 'find' | 'openPointInTimeForType' | 'closePointInTime'>;
}
/**
* @internal
*/
export class PointInTimeFinder {
export interface PointInTimeFinderDependencies
extends SavedObjectsCreatePointInTimeFinderDependencies {
logger: Logger;
}
/** @public */
export interface ISavedObjectsPointInTimeFinder {
/**
* An async generator which wraps calls to `savedObjectsClient.find` and
* iterates over multiple pages of results using `_pit` and `search_after`.
* This will open a new Point-In-Time (PIT), and continue paging until a set
* of results is received that's smaller than the designated `perPage` size.
*/
find: () => AsyncGenerator<SavedObjectsFindResponse>;
/**
* Closes the Point-In-Time associated with this finder instance.
*
* Once you have retrieved all of the results you need, it is recommended
* to call `close()` to clean up the PIT and prevent Elasticsearch from
* consuming resources unnecessarily. This is only required if you are
* done iterating and have not yet paged through all of the results: the
* PIT will automatically be closed for you once you reach the last page
* of results, or if the underlying call to `find` fails for any reason.
*/
close: () => Promise<void>;
}
/**
* @internal
*/
export class PointInTimeFinder implements ISavedObjectsPointInTimeFinder {
readonly #log: Logger;
readonly #savedObjectsClient: SavedObjectsClientContract;
readonly #client: PointInTimeFinderClient;
readonly #findOptions: SavedObjectsFindOptions;
#open: boolean = false;
#pitId?: string;
constructor({
findOptions,
logger,
savedObjectsClient,
}: {
findOptions: SavedObjectsFindOptions;
logger: Logger;
savedObjectsClient: SavedObjectsClientContract;
}) {
this.#log = logger;
this.#savedObjectsClient = savedObjectsClient;
constructor(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
{ logger, client }: PointInTimeFinderDependencies
) {
this.#log = logger.get('point-in-time-finder');
this.#client = client;
this.#findOptions = {
// Default to 1000 items per page as a tradeoff between
// speed and memory consumption.
@ -110,7 +107,7 @@ export class PointInTimeFinder {
lastResultsCount = results.saved_objects.length;
lastHitSortValue = this.getLastHitSortValue(results);
this.#log.debug(`Collected [${lastResultsCount}] saved objects for export.`);
this.#log.debug(`Collected [${lastResultsCount}] saved objects`);
// Close PIT if this was our last page
if (this.#pitId && lastResultsCount < this.#findOptions.perPage!) {
@ -129,7 +126,7 @@ export class PointInTimeFinder {
try {
if (this.#pitId) {
this.#log.debug(`Closing PIT for types [${this.#findOptions.type}]`);
await this.#savedObjectsClient.closePointInTime(this.#pitId);
await this.#client.closePointInTime(this.#pitId);
this.#pitId = undefined;
}
this.#open = false;
@ -141,13 +138,14 @@ export class PointInTimeFinder {
private async open() {
try {
const { id } = await this.#savedObjectsClient.openPointInTimeForType(this.#findOptions.type);
const { id } = await this.#client.openPointInTimeForType(this.#findOptions.type);
this.#pitId = id;
this.#open = true;
} catch (e) {
// Since `find` swallows 404s, it is expected that exporter will do the same,
// Since `find` swallows 404s, it is expected that finder will do the same,
// so we only rethrow non-404 errors here.
if (e.output.statusCode !== 404) {
if (e.output?.statusCode !== 404) {
this.#log.error(`Failed to open PIT for types [${this.#findOptions.type}]`);
throw e;
}
this.#log.debug(`Unable to open PIT for types [${this.#findOptions.type}]: 404 ${e}`);
@ -164,7 +162,7 @@ export class PointInTimeFinder {
searchAfter?: unknown[];
}) {
try {
return await this.#savedObjectsClient.find({
return await this.#client.find({
// Sort fields are required to use searchAfter, so we set some defaults here
sortField: 'updated_at',
sortOrder: 'desc',

View file

@ -6,26 +6,36 @@
* Side Public License, v 1.
*/
import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock';
import { ISavedObjectsRepository } from './repository';
const create = (): jest.Mocked<ISavedObjectsRepository> => ({
checkConflicts: jest.fn(),
create: jest.fn(),
bulkCreate: jest.fn(),
bulkUpdate: jest.fn(),
delete: jest.fn(),
bulkGet: jest.fn(),
find: jest.fn(),
get: jest.fn(),
closePointInTime: jest.fn(),
openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }),
resolve: jest.fn(),
update: jest.fn(),
addToNamespaces: jest.fn(),
deleteFromNamespaces: jest.fn(),
deleteByNamespace: jest.fn(),
incrementCounter: jest.fn(),
removeReferencesTo: jest.fn(),
});
const create = () => {
const mock: jest.Mocked<ISavedObjectsRepository> = {
checkConflicts: jest.fn(),
create: jest.fn(),
bulkCreate: jest.fn(),
bulkUpdate: jest.fn(),
delete: jest.fn(),
bulkGet: jest.fn(),
find: jest.fn(),
get: jest.fn(),
closePointInTime: jest.fn(),
createPointInTimeFinder: jest.fn(),
openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }),
resolve: jest.fn(),
update: jest.fn(),
addToNamespaces: jest.fn(),
deleteFromNamespaces: jest.fn(),
deleteByNamespace: jest.fn(),
incrementCounter: jest.fn(),
removeReferencesTo: jest.fn(),
};
mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({
savedObjectsMock: mock,
});
return mock;
};
export const savedObjectsRepositoryMock = { create };

View file

@ -6,10 +6,14 @@
* Side Public License, v 1.
*/
import { pointInTimeFinderMock } from './repository.test.mock';
import { SavedObjectsRepository } from './repository';
import * as getSearchDslNS from './search_dsl/search_dsl';
import { SavedObjectsErrorHelpers } from './errors';
import { PointInTimeFinder } from './point_in_time_finder';
import { ALL_NAMESPACES_STRING } from './utils';
import { loggerMock } from '../../../logging/logger.mock';
import { SavedObjectsSerializer } from '../../serialization';
import { encodeHitVersion } from '../../version';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
@ -39,6 +43,7 @@ describe('SavedObjectsRepository', () => {
let client;
let savedObjectsRepository;
let migrator;
let logger;
let serializer;
const mockTimestamp = '2017-08-14T15:49:14.886Z';
@ -238,11 +243,13 @@ describe('SavedObjectsRepository', () => {
};
beforeEach(() => {
pointInTimeFinderMock.mockClear();
client = elasticsearchClientMock.createElasticsearchClient();
migrator = mockKibanaMigrator.create();
documentMigrator.prepareMigrations();
migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate);
migrator.runMigrations = async () => ({ status: 'skipped' });
logger = loggerMock.create();
// create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation
serializer = {
@ -269,6 +276,7 @@ describe('SavedObjectsRepository', () => {
typeRegistry: registry,
serializer,
allowedTypes,
logger,
});
savedObjectsRepository._getCurrentTime = jest.fn(() => mockTimestamp);
@ -4632,4 +4640,31 @@ describe('SavedObjectsRepository', () => {
});
});
});
describe('#createPointInTimeFinder', () => {
it('returns a new PointInTimeFinder instance', async () => {
const result = await savedObjectsRepository.createPointInTimeFinder({}, {});
expect(result).toBeInstanceOf(PointInTimeFinder);
});
it('calls PointInTimeFinder with the provided options and dependencies', async () => {
const options = Symbol();
const dependencies = {
client: {
find: Symbol(),
openPointInTimeForType: Symbol(),
closePointInTime: Symbol(),
},
};
await savedObjectsRepository.createPointInTimeFinder(options, dependencies);
expect(pointInTimeFinderMock).toHaveBeenCalledWith(
options,
expect.objectContaining({
...dependencies,
logger,
})
);
});
});
});

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const pointInTimeFinderMock = jest.fn();
jest.doMock('./point_in_time_finder', () => ({
PointInTimeFinder: pointInTimeFinderMock,
}));

View file

@ -13,7 +13,14 @@ import {
GetResponse,
SearchResponse,
} from '../../../elasticsearch/';
import { Logger } from '../../../logging';
import { getRootPropertiesObjects, IndexMapping } from '../../mappings';
import {
ISavedObjectsPointInTimeFinder,
PointInTimeFinder,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
} from './point_in_time_finder';
import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client';
import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
@ -89,6 +96,7 @@ export interface SavedObjectsRepositoryOptions {
serializer: SavedObjectsSerializer;
migrator: IKibanaMigrator;
allowedTypes: string[];
logger: Logger;
}
/**
@ -148,6 +156,7 @@ export class SavedObjectsRepository {
private _allowedTypes: string[];
private readonly client: RepositoryEsClient;
private _serializer: SavedObjectsSerializer;
private _logger: Logger;
/**
* A factory function for creating SavedObjectRepository instances.
@ -162,6 +171,7 @@ export class SavedObjectsRepository {
typeRegistry: SavedObjectTypeRegistry,
indexName: string,
client: ElasticsearchClient,
logger: Logger,
includedHiddenTypes: string[] = [],
injectedConstructor: any = SavedObjectsRepository
): ISavedObjectsRepository {
@ -187,6 +197,7 @@ export class SavedObjectsRepository {
serializer,
allowedTypes,
client,
logger,
});
}
@ -199,6 +210,7 @@ export class SavedObjectsRepository {
serializer,
migrator,
allowedTypes = [],
logger,
} = options;
// It's important that we migrate documents / mark them as up-to-date
@ -218,6 +230,7 @@ export class SavedObjectsRepository {
}
this._allowedTypes = allowedTypes;
this._serializer = serializer;
this._logger = logger;
}
/**
@ -1788,6 +1801,9 @@ export class SavedObjectsRepository {
* Opens a Point In Time (PIT) against the indices for the specified Saved Object types.
* The returned `id` can then be passed to `SavedObjects.find` to search against that PIT.
*
* Only use this API if you have an advanced use case that's not solved by the
* {@link SavedObjectsRepository.createPointInTimeFinder} method.
*
* @example
* ```ts
* const { id } = await savedObjectsClient.openPointInTimeForType(
@ -1853,6 +1869,9 @@ export class SavedObjectsRepository {
* via the Elasticsearch client, and is included in the Saved Objects Client
* as a convenience for consumers who are using `openPointInTimeForType`.
*
* Only use this API if you have an advanced use case that's not solved by the
* {@link SavedObjectsRepository.createPointInTimeFinder} method.
*
* @remarks
* While the `keepAlive` that is provided will cause a PIT to automatically close,
* it is highly recommended to explicitly close a PIT when you are done with it
@ -1896,6 +1915,62 @@ export class SavedObjectsRepository {
return body;
}
/**
* Returns a {@link ISavedObjectsPointInTimeFinder} to help page through
* large sets of saved objects. We strongly recommend using this API for
* any `find` queries that might return more than 1000 saved objects,
* however this API is only intended for use in server-side "batch"
* processing of objects where you are collecting all objects in memory
* or streaming them back to the client.
*
* Do NOT use this API in a route handler to facilitate paging through
* saved objects on the client-side unless you are streaming all of the
* results back to the client at once. Because the returned generator is
* stateful, you cannot rely on subsequent http requests retrieving new
* pages from the same Kibana server in multi-instance deployments.
*
* This generator wraps calls to {@link SavedObjectsRepository.find} and
* iterates over multiple pages of results using `_pit` and `search_after`.
* This will open a new Point-In-Time (PIT), and continue paging until a
* set of results is received that's smaller than the designated `perPage`.
*
* Once you have retrieved all of the results you need, it is recommended
* to call `close()` to clean up the PIT and prevent Elasticsearch from
* consuming resources unnecessarily. This is only required if you are
* done iterating and have not yet paged through all of the results: the
* PIT will automatically be closed for you once you reach the last page
* of results, or if the underlying call to `find` fails for any reason.
*
* @example
* ```ts
* const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
* type: 'visualization',
* search: 'foo*',
* perPage: 100,
* };
*
* const finder = savedObjectsClient.createPointInTimeFinder(findOptions);
*
* const responses: SavedObjectFindResponse[] = [];
* for await (const response of finder.find()) {
* responses.push(...response);
* if (doneSearching) {
* await finder.close();
* }
* }
* ```
*/
createPointInTimeFinder(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
dependencies?: SavedObjectsCreatePointInTimeFinderDependencies
): ISavedObjectsPointInTimeFinder {
return new PointInTimeFinder(findOptions, {
logger: this._logger,
client: this,
...dependencies,
});
}
/**
* Returns index specified by the given type or the default index
*

View file

@ -10,12 +10,14 @@ import { SavedObjectsRepository } from './repository';
import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock';
import { KibanaMigrator } from '../../migrations';
import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { loggerMock, MockedLogger } from '../../../logging/logger.mock';
jest.mock('./repository');
const { SavedObjectsRepository: originalRepository } = jest.requireActual('./repository');
describe('SavedObjectsRepository#createRepository', () => {
let logger: MockedLogger;
const callAdminCluster = jest.fn();
const typeRegistry = new SavedObjectTypeRegistry();
@ -59,6 +61,7 @@ describe('SavedObjectsRepository#createRepository', () => {
const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock<SavedObjectsRepository>;
beforeEach(() => {
logger = loggerMock.create();
RepositoryConstructor.mockClear();
});
@ -69,6 +72,7 @@ describe('SavedObjectsRepository#createRepository', () => {
typeRegistry,
'.kibana-test',
callAdminCluster,
logger,
['unMappedType1', 'unmappedType2']
);
} catch (e) {
@ -84,6 +88,7 @@ describe('SavedObjectsRepository#createRepository', () => {
typeRegistry,
'.kibana-test',
callAdminCluster,
logger,
[],
SavedObjectsRepository
);
@ -102,6 +107,7 @@ describe('SavedObjectsRepository#createRepository', () => {
typeRegistry,
'.kibana-test',
callAdminCluster,
logger,
['hiddenType', 'hiddenType', 'hiddenType'],
SavedObjectsRepository
);

View file

@ -8,9 +8,10 @@
import { SavedObjectsClientContract } from '../types';
import { SavedObjectsErrorHelpers } from './lib/errors';
import { savedObjectsPointInTimeFinderMock } from './lib/point_in_time_finder.mock';
const create = () =>
(({
const create = () => {
const mock = ({
errors: SavedObjectsErrorHelpers,
create: jest.fn(),
bulkCreate: jest.fn(),
@ -21,12 +22,20 @@ const create = () =>
find: jest.fn(),
get: jest.fn(),
closePointInTime: jest.fn(),
createPointInTimeFinder: jest.fn(),
openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }),
resolve: jest.fn(),
update: jest.fn(),
addToNamespaces: jest.fn(),
deleteFromNamespaces: jest.fn(),
removeReferencesTo: jest.fn(),
} as unknown) as jest.Mocked<SavedObjectsClientContract>);
} as unknown) as jest.Mocked<SavedObjectsClientContract>;
mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({
savedObjectsMock: mock,
});
return mock;
};
export const savedObjectsClientMock = { create };

View file

@ -54,6 +54,45 @@ test(`#bulkCreate`, async () => {
expect(result).toBe(returnValue);
});
describe(`#createPointInTimeFinder`, () => {
test(`calls repository with options and default dependencies`, () => {
const returnValue = Symbol();
const mockRepository = {
createPointInTimeFinder: jest.fn().mockReturnValue(returnValue),
};
const client = new SavedObjectsClient(mockRepository);
const options = Symbol();
const result = client.createPointInTimeFinder(options);
expect(mockRepository.createPointInTimeFinder).toHaveBeenCalledWith(options, {
client,
});
expect(result).toBe(returnValue);
});
test(`calls repository with options and custom dependencies`, () => {
const returnValue = Symbol();
const mockRepository = {
createPointInTimeFinder: jest.fn().mockReturnValue(returnValue),
};
const client = new SavedObjectsClient(mockRepository);
const options = Symbol();
const dependencies = {
client: {
find: Symbol(),
openPointInTimeForType: Symbol(),
closePointInTime: Symbol(),
},
};
const result = client.createPointInTimeFinder(options, dependencies);
expect(mockRepository.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies);
expect(result).toBe(returnValue);
});
});
test(`#delete`, async () => {
const returnValue = Symbol();
const mockRepository = {

View file

@ -6,7 +6,12 @@
* Side Public License, v 1.
*/
import { ISavedObjectsRepository } from './lib';
import type {
ISavedObjectsRepository,
ISavedObjectsPointInTimeFinder,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
} from './lib';
import {
SavedObject,
SavedObjectError,
@ -587,6 +592,9 @@ export class SavedObjectsClient {
* Opens a Point In Time (PIT) against the indices for the specified Saved Object types.
* The returned `id` can then be passed to {@link SavedObjectsClient.find} to search
* against that PIT.
*
* Only use this API if you have an advanced use case that's not solved by the
* {@link SavedObjectsClient.createPointInTimeFinder} method.
*/
async openPointInTimeForType(
type: string | string[],
@ -599,8 +607,67 @@ export class SavedObjectsClient {
* Closes a Point In Time (PIT) by ID. This simply proxies the request to ES via the
* Elasticsearch client, and is included in the Saved Objects Client as a convenience
* for consumers who are using {@link SavedObjectsClient.openPointInTimeForType}.
*
* Only use this API if you have an advanced use case that's not solved by the
* {@link SavedObjectsClient.createPointInTimeFinder} method.
*/
async closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions) {
return await this._repository.closePointInTime(id, options);
}
/**
* Returns a {@link ISavedObjectsPointInTimeFinder} to help page through
* large sets of saved objects. We strongly recommend using this API for
* any `find` queries that might return more than 1000 saved objects,
* however this API is only intended for use in server-side "batch"
* processing of objects where you are collecting all objects in memory
* or streaming them back to the client.
*
* Do NOT use this API in a route handler to facilitate paging through
* saved objects on the client-side unless you are streaming all of the
* results back to the client at once. Because the returned generator is
* stateful, you cannot rely on subsequent http requests retrieving new
* pages from the same Kibana server in multi-instance deployments.
*
* The generator wraps calls to {@link SavedObjectsClient.find} and iterates
* over multiple pages of results using `_pit` and `search_after`. This will
* open a new Point-In-Time (PIT), and continue paging until a set of
* results is received that's smaller than the designated `perPage`.
*
* Once you have retrieved all of the results you need, it is recommended
* to call `close()` to clean up the PIT and prevent Elasticsearch from
* consuming resources unnecessarily. This is only required if you are
* done iterating and have not yet paged through all of the results: the
* PIT will automatically be closed for you once you reach the last page
* of results, or if the underlying call to `find` fails for any reason.
*
* @example
* ```ts
* const findOptions: SavedObjectsCreatePointInTimeFinderOptions = {
* type: 'visualization',
* search: 'foo*',
* perPage: 100,
* };
*
* const finder = savedObjectsClient.createPointInTimeFinder(findOptions);
*
* const responses: SavedObjectFindResponse[] = [];
* for await (const response of finder.find()) {
* responses.push(...response);
* if (doneSearching) {
* await finder.close();
* }
* }
* ```
*/
createPointInTimeFinder(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
dependencies?: SavedObjectsCreatePointInTimeFinderDependencies
): ISavedObjectsPointInTimeFinder {
return this._repository.createPointInTimeFinder(findOptions, {
client: this,
// Include dependencies last so that SO client wrappers have their settings applied.
...dependencies,
});
}
}

View file

@ -1177,6 +1177,12 @@ export type ISavedObjectsExporter = PublicMethodsOf<SavedObjectsExporter>;
// @public (undocumented)
export type ISavedObjectsImporter = PublicMethodsOf<SavedObjectsImporter>;
// @public (undocumented)
export interface ISavedObjectsPointInTimeFinder {
close: () => Promise<void>;
find: () => AsyncGenerator<SavedObjectsFindResponse>;
}
// @public
export type ISavedObjectsRepository = Pick<SavedObjectsRepository, keyof SavedObjectsRepository>;
@ -2219,6 +2225,7 @@ export class SavedObjectsClient {
checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise<SavedObjectsCheckConflictsResponse>;
closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions): Promise<SavedObjectsClosePointInTimeResponse>;
create<T = unknown>(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise<SavedObject<T>>;
createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder;
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<SavedObjectsDeleteFromNamespacesResponse>;
// (undocumented)
@ -2321,6 +2328,15 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
version?: string;
}
// @public (undocumented)
export interface SavedObjectsCreatePointInTimeFinderDependencies {
// (undocumented)
client: Pick<SavedObjectsClientContract, 'find' | 'openPointInTimeForType' | 'closePointInTime'>;
}
// @public (undocumented)
export type SavedObjectsCreatePointInTimeFinderOptions = Omit<SavedObjectsFindOptions, 'page' | 'pit' | 'searchAfter'>;
// @public (undocumented)
export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions {
refresh?: boolean;
@ -2811,10 +2827,11 @@ export class SavedObjectsRepository {
checkConflicts(objects?: SavedObjectsCheckConflictsObject[], options?: SavedObjectsBaseOptions): Promise<SavedObjectsCheckConflictsResponse>;
closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions): Promise<SavedObjectsClosePointInTimeResponse>;
create<T = unknown>(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise<SavedObject<T>>;
createPointInTimeFinder(findOptions: SavedObjectsCreatePointInTimeFinderOptions, dependencies?: SavedObjectsCreatePointInTimeFinderDependencies): ISavedObjectsPointInTimeFinder;
// Warning: (ae-forgotten-export) The symbol "IKibanaMigrator" needs to be exported by the entry point index.d.ts
//
// @internal
static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
static createRepository(migrator: IKibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, client: ElasticsearchClient, logger: Logger, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository;
delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise<any>;
deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<SavedObjectsDeleteFromNamespacesResponse>;

View file

@ -1226,7 +1226,7 @@ export class Plugin implements Plugin_2<PluginSetup, PluginStart, DataPluginSetu
fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise<import("../common").FieldFormatsRegistry>;
};
indexPatterns: {
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "closePointInTime" | "create" | "update" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
indexPatternsServiceFactory: (savedObjectsClient: Pick<import("../../../core/server").SavedObjectsClient, "get" | "delete" | "create" | "bulkCreate" | "checkConflicts" | "find" | "bulkGet" | "resolve" | "update" | "addToNamespaces" | "deleteFromNamespaces" | "bulkUpdate" | "removeReferencesTo" | "openPointInTimeForType" | "closePointInTime" | "createPointInTimeFinder" | "errors">, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise<import(".").IndexPatternsService>;
};
search: ISearchStart<import("./search").IEsSearchRequest, import("./search").IEsSearchResponse<any>>;
};

View file

@ -1820,3 +1820,30 @@ describe('#closePointInTime', () => {
expect(mockBaseClient.closePointInTime).toHaveBeenCalledTimes(1);
});
});
describe('#createPointInTimeFinder', () => {
it('redirects request to underlying base client with default dependencies', () => {
const options = { type: ['a', 'b'], search: 'query' };
wrapper.createPointInTimeFinder(options);
expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, {
client: wrapper,
});
});
it('redirects request to underlying base client with custom dependencies', () => {
const options = { type: ['a', 'b'], search: 'query' };
const dependencies = {
client: {
find: jest.fn(),
openPointInTimeForType: jest.fn(),
closePointInTime: jest.fn(),
},
};
wrapper.createPointInTimeFinder(options, dependencies);
expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies);
});
});

View file

@ -19,6 +19,8 @@ import type {
SavedObjectsClientContract,
SavedObjectsClosePointInTimeOptions,
SavedObjectsCreateOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsDeleteFromNamespacesOptions,
SavedObjectsFindOptions,
SavedObjectsFindResponse,
@ -263,6 +265,17 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon
return await this.options.baseClient.closePointInTime(id, options);
}
public createPointInTimeFinder(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
dependencies?: SavedObjectsCreatePointInTimeFinderDependencies
) {
return this.options.baseClient.createPointInTimeFinder(findOptions, {
client: this,
// Include dependencies last so that subsequent SO client wrappers have their settings applied.
...dependencies,
});
}
/**
* Strips encrypted attributes from any non-bulk Saved Objects API response. If type isn't
* registered, response is returned as is.

View file

@ -1058,6 +1058,36 @@ describe('#closePointInTime', () => {
});
});
describe('#createPointInTimeFinder', () => {
it('redirects request to underlying base client with default dependencies', () => {
const options = { type: ['a', 'b'], search: 'query' };
client.createPointInTimeFinder(options);
expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, {
client,
});
});
it('redirects request to underlying base client with custom dependencies', () => {
const options = { type: ['a', 'b'], search: 'query' };
const dependencies = {
client: {
find: jest.fn(),
openPointInTimeForType: jest.fn(),
closePointInTime: jest.fn(),
},
};
client.createPointInTimeFinder(options, dependencies);
expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith(
options,
dependencies
);
});
});
describe('#resolve', () => {
const type = 'foo';
const id = `${type}-id`;

View file

@ -16,6 +16,8 @@ import type {
SavedObjectsClientContract,
SavedObjectsClosePointInTimeOptions,
SavedObjectsCreateOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsDeleteFromNamespacesOptions,
SavedObjectsFindOptions,
SavedObjectsOpenPointInTimeOptions,
@ -616,6 +618,20 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra
return await this.baseClient.closePointInTime(id, options);
}
public createPointInTimeFinder(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
dependencies?: SavedObjectsCreatePointInTimeFinderDependencies
) {
// We don't need to perform an authorization check here or add an audit log, because
// `createPointInTimeFinder` is simply a helper that calls `find`, `openPointInTimeForType`,
// and `closePointInTime` internally, so authz checks and audit logs will already be applied.
return this.baseClient.createPointInTimeFinder(findOptions, {
client: this,
// Include dependencies last so that subsequent SO client wrappers have their settings applied.
...dependencies,
});
}
private async checkPrivileges(
actions: string | string[],
namespaceOrNamespaces?: string | Array<undefined | string>

View file

@ -643,5 +643,43 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces';
});
});
});
describe('#createPointInTimeFinder', () => {
test(`throws error if options.namespace is specified`, async () => {
const { client } = createSpacesSavedObjectsClient();
const options = { type: ['a', 'b'], search: 'query', namespace: 'oops' };
expect(() => client.createPointInTimeFinder(options)).toThrow(ERROR_NAMESPACE_SPECIFIED);
});
it('redirects request to underlying base client with default dependencies', () => {
const { client, baseClient } = createSpacesSavedObjectsClient();
const options = { type: ['a', 'b'], search: 'query' };
client.createPointInTimeFinder(options);
expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, {
client,
});
});
it('redirects request to underlying base client with custom dependencies', () => {
const { client, baseClient } = createSpacesSavedObjectsClient();
const options = { type: ['a', 'b'], search: 'query' };
const dependencies = {
client: {
find: jest.fn(),
openPointInTimeForType: jest.fn(),
closePointInTime: jest.fn(),
},
};
client.createPointInTimeFinder(options, dependencies);
expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1);
expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies);
});
});
});
});

View file

@ -18,6 +18,8 @@ import type {
SavedObjectsClientContract,
SavedObjectsClosePointInTimeOptions,
SavedObjectsCreateOptions,
SavedObjectsCreatePointInTimeFinderDependencies,
SavedObjectsCreatePointInTimeFinderOptions,
SavedObjectsDeleteFromNamespacesOptions,
SavedObjectsFindOptions,
SavedObjectsOpenPointInTimeOptions,
@ -420,4 +422,31 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract {
namespace: spaceIdToNamespace(this.spaceId),
});
}
/**
* Returns a generator to help page through large sets of saved objects.
*
* The generator wraps calls to `SavedObjects.find` and iterates over
* multiple pages of results using `_pit` and `search_after`. This will
* open a new Point In Time (PIT), and continue paging until a set of
* results is received that's smaller than the designated `perPage`.
*
* @param {object} findOptions - {@link SavedObjectsCreatePointInTimeFinderOptions}
* @param {object} [dependencies] - {@link SavedObjectsCreatePointInTimeFinderDependencies}
*/
createPointInTimeFinder(
findOptions: SavedObjectsCreatePointInTimeFinderOptions,
dependencies?: SavedObjectsCreatePointInTimeFinderDependencies
) {
throwErrorIfNamespaceSpecified(findOptions);
// We don't need to handle namespaces here, because `createPointInTimeFinder`
// is simply a helper that calls `find`, `openPointInTimeForType`, and
// `closePointInTime` internally, so namespaces will already be handled
// in those methods.
return this.client.createPointInTimeFinder(findOptions, {
client: this,
// Include dependencies last so that subsequent SO client wrappers have their settings applied.
...dependencies,
});
}
}