import { isNotFoundFromUnsupportedServer } from '../../../elasticsearch' ; import type { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry' ; import type { SavedObjectsSerializer } from '../../serialization' ; import type { SavedObject , SavedObjectsBaseOptions } from '../../types' ; import { SavedObjectsErrorHelpers } from './errors' ; import { findLegacyUrlAliases } from './legacy_url_aliases' ; import { getRootFields } from './included_fields' ; import { getObjectKey , getSavedObjectFromSource , parseObjectKey , rawDocExistsInNamespace , } from './internal_utils' ; import type { CreatePointInTimeFinderFn } from './point_in_time_finder' ; import type { RepositoryEsClient } from './repository_es_client' ; /**
 * When we collect an object's outbound references, we will only go a maximum of this many levels deep before we throw an error.
 */ const MAX_REFERENCE_GRAPH_DEPTH = 20 ; internal export const ALIAS_SEARCH_PER_PAGE = 100 ; public export interface SavedObjectsCollectMultiNamespaceReferencesObject { id : string ; type : string ; } public export interface SavedObjectsCollectMultiNamespaceReferencesOptions extends SavedObjectsBaseOptions { /** Optional purpose used to determine filtering and authorization checks; default is 'collectMultiNamespaceReferences' */ purpose ? : 'collectMultiNamespaceReferences' | 'updateObjectsSpaces' ; } public export interface SavedObjectReferenceWithContext { /** The type of the referenced object */ type : string ; /** The ID of the referenced object */ id : string ; /** The space(s) that the referenced object exists in */ spaces : string [ ] ; /**
     * References to this object; note that this does not contain _all inbound references everywhere for this object_, it only contains
     * inbound references for the scope of this operation
     */ inboundReferences : Array < { /** The type of the object that has the inbound reference */ type : string ; /** The ID of the object that has the inbound reference */ id : string ; /** The name of the inbound reference */ name : string ; } > ; /** Whether or not this object or reference is missing */ isMissing ? : boolean ; /** The space(s) that legacy URL aliases matching this type/id exist in */ spacesWithMatchingAliases ? : string [ ] ; } public export interface SavedObjectsCollectMultiNamespaceReferencesResponse { objects : SavedObjectReferenceWithContext [ ] ; } internal export interface CollectMultiNamespaceReferencesParams { registry : ISavedObjectTypeRegistry ; allowedTypes : string [ ] ; client : RepositoryEsClient ; serializer : SavedObjectsSerializer ; getIndexForType : ( type : string ) => string ; createPointInTimeFinder : CreatePointInTimeFinderFn ; objects : SavedObjectsCollectMultiNamespaceReferencesObject [ ] ; options ? : SavedObjectsCollectMultiNamespaceReferencesOptions ; } internal export async function collectMultiNamespaceReferences ( params ) { const { createPointInTimeFinder , objects } = params ; if ( ! objects . length ) { return { objects : [ ] } ; } const { objectMap , inboundReferencesMap } = await getObjectsAndReferences ( params ) ; const objectsWithContext = Array . from ( inboundReferencesMap . entries ( ) ) . map < SavedObjectReferenceWithContext > ( ( [ referenceKey , referenceVal ] ) => { const inboundReferences = Array . from ( referenceVal . entries ( ) ) . map ( ( [ objectKey , name ] ) => { const { type , id } = parseObjectKey ( objectKey ) ; return { type , id , name } ; } ) ; const { type , id } = parseObjectKey ( referenceKey ) ; const object = objectMap . get ( referenceKey ) ; const spaces = object ?. namespaces ?? [ ] ; return { type , id , spaces , inboundReferences , ... ( object === null && { isMissing : true } ) } ; } ) ; const objectsToFindAliasesFor = objectsWithContext . filter ( ( { spaces } ) => spaces . length !== 0 ) . map ( ( { type , id } ) => ( { type , id } ) ) ; const aliasesMap = await findLegacyUrlAliases ( createPointInTimeFinder , objectsToFindAliasesFor , ALIAS_SEARCH_PER_PAGE ) ; const results = objectsWithContext . map ( ( obj ) => { const key = getObjectKey ( obj ) ; const val = aliasesMap . get ( key ) ; const spacesWithMatchingAliases = val && Array . from ( val ) ; return { ... obj , spacesWithMatchingAliases } ; } ) ; return { objects : results , } ; } /**
 * Recursively fetches objects and their references, returning a map of the retrieved objects and a map of all inbound references.
 */ async function getObjectsAndReferences ( { registry , allowedTypes , client , serializer , getIndexForType , objects , options = { } , } ) { const { namespace , purpose } = options ; const inboundReferencesMap = objects . reduce ( ( acc , cur ) => acc . set ( getObjectKey ( cur ) , new Map ( ) ) , new Map < string , Map < string , string > > ( ) ) ; const objectMap = new Map < string , SavedObject | null > ( ) ; const rootFields = getRootFields ( ) ; const makeBulkGetDocs = ( objectsToGet ) => objectsToGet . map ( ( { type , id } ) => ( { _id : serializer . generateRawId ( undefined , type , id ) , _index : getIndexForType ( type ) , _source : rootFields , } ) ) ; const validObjectTypesFilter = ( { type } ) => allowedTypes . includes ( type ) && ( purpose === 'updateObjectsSpaces' ? registry . isShareable ( type ) : registry . isMultiNamespace ( type ) ) ; let bulkGetObjects = objects . filter ( validObjectTypesFilter ) ; let count = 0 ; while ( bulkGetObjects . length ) { if ( count >= MAX_REFERENCE_GRAPH_DEPTH ) { throw new Error ( `Exceeded maximum reference graph depth of ${ MAX_REFERENCE_GRAPH_DEPTH } objects!` ) ; } const bulkGetResponse = await client . mget ( { body : { docs : makeBulkGetDocs ( bulkGetObjects ) } } , { ignore : [ 404 ] } ) ; if ( isNotFoundFromUnsupportedServer ( { statusCode : bulkGetResponse . statusCode , headers : bulkGetResponse . headers , } ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundEsUnavailableError ( ) ; } const newObjectsToGet = new Set < string > ( ) ; for ( let i = 0 ; i < bulkGetObjects . length ; i ++ ) { const { type , id } = bulkGetObjects [ i ] ; const objectKey = getObjectKey ( { type , id } ) ; const doc = bulkGetResponse . body . docs [ i ] ; if ( ! doc . found || ! rawDocExistsInNamespace ( registry , doc , namespace ) ) { objectMap . set ( objectKey , null ) ; continue ; } const object = getSavedObjectFromSource ( registry , type , id , doc ) ; objectMap . set ( objectKey , object ) ; for ( const reference of object . references ) { if ( ! validObjectTypesFilter ( reference ) ) { continue ; } const referenceKey = getObjectKey ( reference ) ; const referenceVal = inboundReferencesMap . get ( referenceKey ) ?? new Map < string , string > ( ) ; if ( ! referenceVal . has ( objectKey ) ) { inboundReferencesMap . set ( referenceKey , referenceVal . set ( objectKey , reference . name ) ) ; } if ( ! objectMap . has ( referenceKey ) ) { newObjectsToGet . add ( referenceKey ) ; } } } bulkGetObjects = Array . from ( newObjectsToGet ) . map ( ( key ) => parseObjectKey ( key ) ) ; count ++ ; } return { objectMap , inboundReferencesMap } ; }