1717 * under the License.
1818 */
1919
20- import { cloneDeep , pick , throttle } from 'lodash' ;
20+ import { pick , throttle , cloneDeep } from 'lodash' ;
2121import { resolve as resolveUrl } from 'url' ;
2222import type { PublicMethodsOf } from '@kbn/utility-types' ;
2323
@@ -144,6 +144,23 @@ const API_BASE_URL = '/api/saved_objects/';
144144 */
145145export type SavedObjectsClientContract = PublicMethodsOf < SavedObjectsClient > ;
146146
147+ interface ObjectTypeAndId {
148+ id : string ;
149+ type : string ;
150+ }
151+
152+ const getObjectsToFetch = ( queue : BatchQueueEntry [ ] ) : ObjectTypeAndId [ ] => {
153+ const objects : ObjectTypeAndId [ ] = [ ] ;
154+ const inserted = new Set < string > ( ) ;
155+ queue . forEach ( ( { id, type } ) => {
156+ if ( ! inserted . has ( `${ type } |${ id } ` ) ) {
157+ objects . push ( { id, type } ) ;
158+ inserted . add ( `${ type } |${ id } ` ) ;
159+ }
160+ } ) ;
161+ return objects ;
162+ } ;
163+
147164/**
148165 * Saved Objects is Kibana's data persisentence mechanism allowing plugins to
149166 * use Elasticsearch for storing plugin state. The client-side
@@ -160,31 +177,34 @@ export class SavedObjectsClient {
160177 * Throttled processing of get requests into bulk requests at 100ms interval
161178 */
162179 private processBatchQueue = throttle (
163- ( ) => {
164- const queue = cloneDeep ( this . batchQueue ) ;
180+ async ( ) => {
181+ const queue = [ ... this . batchQueue ] ;
165182 this . batchQueue = [ ] ;
166183
167- this . bulkGet ( queue )
168- . then ( ( { savedObjects } ) => {
169- queue . forEach ( ( queueItem ) => {
170- const foundObject = savedObjects . find ( ( savedObject ) => {
171- return savedObject . id === queueItem . id && savedObject . type === queueItem . type ;
172- } ) ;
184+ try {
185+ const objectsToFetch = getObjectsToFetch ( queue ) ;
186+ const { saved_objects : savedObjects } = await this . performBulkGet ( objectsToFetch ) ;
173187
174- if ( ! foundObject ) {
175- return queueItem . resolve (
176- this . createSavedObject ( pick ( queueItem , [ 'id' , 'type' ] ) as SavedObject )
177- ) ;
178- }
179-
180- queueItem . resolve ( foundObject ) ;
181- } ) ;
182- } )
183- . catch ( ( err ) => {
184- queue . forEach ( ( queueItem ) => {
185- queueItem . reject ( err ) ;
188+ queue . forEach ( ( queueItem ) => {
189+ const foundObject = savedObjects . find ( ( savedObject ) => {
190+ return savedObject . id === queueItem . id && savedObject . type === queueItem . type ;
186191 } ) ;
192+
193+ if ( foundObject ) {
194+ // multiple calls may have been requested the same object.
195+ // we need to clone to avoid sharing references between the instances
196+ queueItem . resolve ( this . createSavedObject ( cloneDeep ( foundObject ) ) ) ;
197+ } else {
198+ queueItem . resolve (
199+ this . createSavedObject ( pick ( queueItem , [ 'id' , 'type' ] ) as SavedObject )
200+ ) ;
201+ }
187202 } ) ;
203+ } catch ( err ) {
204+ queue . forEach ( ( queueItem ) => {
205+ queueItem . reject ( err ) ;
206+ } ) ;
207+ }
188208 } ,
189209 BATCH_INTERVAL ,
190210 { leading : false }
@@ -373,14 +393,8 @@ export class SavedObjectsClient {
373393 * ])
374394 */
375395 public bulkGet = ( objects : Array < { id : string ; type : string } > = [ ] ) => {
376- const path = this . getPath ( [ '_bulk_get' ] ) ;
377396 const filteredObjects = objects . map ( ( obj ) => pick ( obj , [ 'id' , 'type' ] ) ) ;
378-
379- const request : ReturnType < SavedObjectsApi [ 'bulkGet' ] > = this . savedObjectsFetch ( path , {
380- method : 'POST' ,
381- body : JSON . stringify ( filteredObjects ) ,
382- } ) ;
383- return request . then ( ( resp ) => {
397+ return this . performBulkGet ( filteredObjects ) . then ( ( resp ) => {
384398 resp . saved_objects = resp . saved_objects . map ( ( d ) => this . createSavedObject ( d ) ) ;
385399 return renameKeys <
386400 PromiseType < ReturnType < SavedObjectsApi [ 'bulkGet' ] > > ,
@@ -389,6 +403,15 @@ export class SavedObjectsClient {
389403 } ) ;
390404 } ;
391405
406+ private async performBulkGet ( objects : ObjectTypeAndId [ ] ) {
407+ const path = this . getPath ( [ '_bulk_get' ] ) ;
408+ const request : ReturnType < SavedObjectsApi [ 'bulkGet' ] > = this . savedObjectsFetch ( path , {
409+ method : 'POST' ,
410+ body : JSON . stringify ( objects ) ,
411+ } ) ;
412+ return request ;
413+ }
414+
392415 /**
393416 * Updates an object
394417 *
0 commit comments