import { Serializable } from '@kbn/utility-types' ; import { get , flow , mapValues } from 'lodash' ; import { SavedObjectAttributes , SavedObjectMigrationFn , SavedObjectMigrationMap , } from 'kibana/server' ; import { migrations730 } from './migrations_730' ; import { SavedDashboardPanel } from '../../common/types' ; import { EmbeddableSetup } from '../../../embeddable/server' ; import { migrateMatchAllQuery } from './migrate_match_all_query' ; import { DashboardDoc700To720 , DashboardDoc730ToLatest } from '../../common' ; import { injectReferences , extractReferences } from '../../common/saved_dashboard_references' ; import { convertPanelStateToSavedDashboardPanel , convertSavedDashboardPanelToPanelState , } from '../../common/embeddable/embeddable_saved_object_converters' ; import { SavedObjectEmbeddableInput } from '../../../embeddable/common' ; import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../data/common' ; import { mergeMigrationFunctionMaps , MigrateFunction , MigrateFunctionsObject , } from '../../../kibana_utils/common' ; import { replaceIndexPatternReference } from './replace_index_pattern_reference' ; function migrateIndexPattern ( doc ) { const searchSourceJSON = get ( doc , 'attributes.kibanaSavedObjectMeta.searchSourceJSON' ) ; if ( typeof searchSourceJSON !== 'string' ) { return ; } let searchSource ; try { searchSource = JSON . parse ( searchSourceJSON ) ; } catch ( e ) { return ; } if ( searchSource . index ) { searchSource . indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index' ; doc . references . push ( { name : searchSource . indexRefName , type : INDEX_PATTERN_SAVED_OBJECT_TYPE , id : searchSource . index , } ) ; delete searchSource . index ; } if ( searchSource . filter ) { searchSource . filter . forEach ( ( filterRow , i ) => { if ( ! filterRow . meta || ! filterRow . meta . index ) { return ; } filterRow . meta . indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${ i }].meta.index` ; doc . references . push ( { name : filterRow . meta . indexRefName , type : INDEX_PATTERN_SAVED_OBJECT_TYPE , id : filterRow . meta . index , } ) ; delete filterRow . meta . index ; } ) ; } doc . attributes . kibanaSavedObjectMeta . searchSourceJSON = JSON . stringify ( searchSource ) ; } const migrations700 = ( doc ) : DashboardDoc700To720 => { doc . references = doc . references || [ ] ; migrateIndexPattern ( doc as DashboardDoc700To720 ) ; const panelsJSON = get ( doc , 'attributes.panelsJSON' ) ; if ( typeof panelsJSON !== 'string' ) { return doc as DashboardDoc700To720 ; } let panels ; try { panels = JSON . parse ( panelsJSON ) ; } catch ( e ) { return doc as DashboardDoc700To720 ; } if ( ! Array . isArray ( panels ) ) { return doc as DashboardDoc700To720 ; } panels . forEach ( ( panel , i ) => { if ( ! panel . type || ! panel . id ) { return ; } panel . panelRefName = `panel_${ i }` ; doc . references ! . push ( { name : `panel_${ i }` , type : panel . type , id : panel . id , } ) ; delete panel . type ; delete panel . id ; } ) ; doc . attributes . panelsJSON = JSON . stringify ( panels ) ; return doc as DashboardDoc700To720 ; } ; param deps function createExtractPanelReferencesMigration ( deps ) { return ( doc ) => { const references = doc . references ?? [ ] ; /**
         * Remembering this because dashboard's extractReferences won't return those
         * All other references like `panel_` will be overwritten
         */ const oldNonPanelReferences = references . filter ( ( ref ) => ! ref . name . startsWith ( 'panel_' ) ) ; const injectedAttributes = injectReferences ( { attributes : doc . attributes as unknown as SavedObjectAttributes , references , } , { embeddablePersistableStateService : deps . embeddable } ) ; const { attributes , references : newPanelReferences } = extractReferences ( { attributes : injectedAttributes , references : [ ] } , { embeddablePersistableStateService : deps . embeddable } ) ; return { ... doc , references : [ ... oldNonPanelReferences , ... newPanelReferences ] , attributes , } ; } ; } type ValueOrReferenceInput = SavedObjectEmbeddableInput & { attributes ? : Serializable ; savedVis ? : Serializable ; } ; const migrateByValuePanels = ( migrate , version ) : SavedObjectMigrationFn => ( doc ) => { const { attributes } = doc ; if ( typeof attributes ?. panelsJSON !== 'string' ) { return doc ; } const panels = JSON . parse ( attributes . panelsJSON ) as SavedDashboardPanel [ ] ; if ( ! Array . isArray ( panels ) ) { return doc ; } const newPanels = [ ] ; panels . forEach ( ( panel ) => { const originalPanelState = convertSavedDashboardPanelToPanelState < ValueOrReferenceInput > ( panel ) ; if ( originalPanelState . explicitInput . attributes || originalPanelState . explicitInput . savedVis ) { const migratedInput = migrate ( { ... originalPanelState . explicitInput , type : originalPanelState . type , } ) ; newPanels . push ( convertPanelStateToSavedDashboardPanel ( { ... originalPanelState , explicitInput : { ... migratedInput , id : migratedInput . id as string } , } , version ) ) ; } else { newPanels . push ( panel ) ; } } ) ; return { ... doc , attributes : { ... attributes , panelsJSON : JSON . stringify ( newPanels ) , } , } ; } ; export interface DashboardSavedObjectTypeMigrationsDeps { embeddable : EmbeddableSetup ; } export const createDashboardSavedObjectTypeMigrations = ( deps ) : SavedObjectMigrationMap => { const embeddableMigrations = mapValues < MigrateFunctionsObject , SavedObjectMigrationFn > ( deps . embeddable . getAllMigrations ( ) , migrateByValuePanels ) as MigrateFunctionsObject ; const dashboardMigrations = { /**
         * We need to have this migration twice, once with a version prior to 7.0.0 once with a version
         * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already
         * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running,
         * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we
         * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects
         * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced
         * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7
         * only contained the 6.7.2 migration and not the 7.0.1 migration.
         */ '6.7.2' : flow ( migrateMatchAllQuery ) , '7.0.0' : flow ( migrations700 ) , '7.3.0' : flow ( migrations730 ) , '7.9.3' : flow ( migrateMatchAllQuery ) , '7.11.0' : flow ( createExtractPanelReferencesMigration ( deps ) ) , /**
         * Any dashboard saved object migrations that come after this point will have to be wary of
         * potentially overwriting embeddable migrations. An example of how to mitigate this follows:
         */ '7.14.0' : flow ( replaceIndexPatternReference ) , } ; return mergeMigrationFunctionMaps ( dashboardMigrations , embeddableMigrations ) ; } ;