import { i18n } from '@kbn/i18n' ; import { first } from 'rxjs/operators' ; import type { SavedObjectMetaData , OnSaveProps } from 'src/plugins/saved_objects/public' ; import type { EmbeddableStateWithType } from 'src/plugins/embeddable/common' ; import { extractSearchSourceReferences } from '../../../data/public' ; import type { SavedObjectAttributes , SavedObjectReference } from '../../../../core/public' ; import { EmbeddableFactoryDefinition , EmbeddableOutput , ErrorEmbeddable , IContainer , AttributeService , } from '../../../embeddable/public' ; import type { DisabledLabEmbeddable } from './disabled_lab_embeddable' ; import type { VisualizeByReferenceInput , VisualizeByValueInput , VisualizeEmbeddable , VisualizeInput , VisualizeOutput , VisualizeSavedObjectAttributes , } from './visualize_embeddable' ; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants' ; import type { SerializedVis , Vis } from '../vis' ; import { createVisAsync } from '../vis_async' ; import { getCapabilities , getTypes , getUISettings } from '../services' ; import { showNewVisModal } from '../wizard' ; import { convertToSerializedVis , getSavedVisualization , saveVisualization , getFullPath , } from '../utils/saved_visualize_utils' ; import { extractControlsReferences , extractTimeSeriesReferences , injectTimeSeriesReferences , injectControlsReferences , } from '../utils/saved_visualization_references' ; import { createVisEmbeddableFromObject } from './create_vis_embeddable_from_object' ; import { VISUALIZE_ENABLE_LABS_SETTING } from '../../common/constants' ; import { checkForDuplicateTitle } from '../../../saved_objects/public' ; import type { StartServicesGetter } from '../../../kibana_utils/public' ; import type { VisualizationsStartDeps } from '../plugin' ; interface VisualizationAttributes extends SavedObjectAttributes { visState : string ; } export interface VisualizeEmbeddableFactoryDeps { start : StartServicesGetter < Pick < VisualizationsStartDeps , 'inspector' | 'embeddable' | 'savedObjectsClient' | 'data' | 'savedObjectsTaggingOss' | 'spaces' > > ; } export class VisualizeEmbeddableFactory implements EmbeddableFactoryDefinition < VisualizeInput , VisualizeOutput | EmbeddableOutput , VisualizeEmbeddable | DisabledLabEmbeddable , VisualizationAttributes > { public readonly type = VISUALIZE_EMBEDDABLE_TYPE ; private attributeService ? : AttributeService < VisualizeSavedObjectAttributes , VisualizeByValueInput , VisualizeByReferenceInput > ; public readonly savedObjectMetaData : SavedObjectMetaData < VisualizationAttributes > = { name : i18n . translate ( 'visualizations.savedObjectName' , { defaultMessage : 'Visualization' } ) , includeFields : [ 'visState' ] , type : 'visualization' , getIconForSavedObject : ( savedObject ) => { return ( getTypes ( ) . get ( JSON . parse ( savedObject . attributes . visState ) . type ) ?. icon || 'visualizeApp' ) ; } , getTooltipForSavedObject : ( savedObject ) => { return `${ savedObject . attributes . title } (${ getTypes ( ) . get ( JSON . parse ( savedObject . attributes . visState ) . type ) ?. title })` ; } , showSavedObject : ( savedObject ) => { const typeName = JSON . parse ( savedObject . attributes . visState ) . type ; const visType = getTypes ( ) . get ( typeName ) ; if ( ! visType ) { return false ; } if ( getUISettings ( ) . get ( VISUALIZE_ENABLE_LABS_SETTING ) ) { return true ; } return visType . stage !== 'experimental' ; } , getSavedObjectSubType : ( savedObject ) => { return JSON . parse ( savedObject . attributes . visState ) . type ; } , } ; constructor ( private readonly deps ) { } public async isEditable ( ) { return getCapabilities ( ) . visualize . save as boolean ; } public getDisplayName ( ) { return i18n . translate ( 'visualizations.displayName' , { defaultMessage : 'Visualization' , } ) ; } public async getCurrentAppId ( ) { return await this . deps . start ( ) . core . application . currentAppId$ . pipe ( first ( ) ) . toPromise ( ) ; } private async getAttributeService ( ) { if ( ! this . attributeService ) { this . attributeService = await this . deps . start ( ) . plugins . embeddable . getAttributeService < VisualizeSavedObjectAttributes , VisualizeByValueInput , VisualizeByReferenceInput > ( this . type , { saveMethod : this . saveMethod . bind ( this ) , checkForDuplicateTitle : this . checkTitle . bind ( this ) , } ) ; } return this . attributeService ! ; } public async createFromSavedObject ( savedObjectId , input , parent ? ) { const startDeps = await this . deps . start ( ) ; try { const savedObject = await getSavedVisualization ( { savedObjectsClient : startDeps . core . savedObjects . client , search : startDeps . plugins . data . search , dataViews : startDeps . plugins . data . dataViews , spaces : startDeps . plugins . spaces , savedObjectsTagging : startDeps . plugins . savedObjectsTaggingOss ?. getTaggingApi ( ) , } , savedObjectId ) ; if ( savedObject . sharingSavedObjectProps ?. outcome === 'conflict' ) { return new ErrorEmbeddable ( i18n . translate ( 'visualizations.embeddable.legacyURLConflict.errorMessage' , { defaultMessage : `This visualization has the same URL as a legacy alias. Disable the alias to resolve this error : {json}` , values : { json : savedObject . sharingSavedObjectProps ?. errorJSON } , } ) , input , parent ) ; } const visState = convertToSerializedVis ( savedObject ) ; const vis = await createVisAsync ( savedObject . visState . type , visState ) ; return createVisEmbeddableFromObject ( this . deps ) ( vis , input , await this . getAttributeService ( ) , parent ) ; } catch ( e ) { console . error ( e ) ; return new ErrorEmbeddable ( e , input , parent ) ; } } public async create ( input , parent ? ) { if ( input . savedVis ) { const visState = input . savedVis ; const vis = await createVisAsync ( visState . type , visState ) ; return createVisEmbeddableFromObject ( this . deps ) ( vis , input , await this . getAttributeService ( ) , parent ) ; } else { showNewVisModal ( { originatingApp : await this . getCurrentAppId ( ) , outsideVisualizeApp : true , } ) ; return undefined ; } } private async saveMethod ( attributes ) { try { const { title , savedVis } = attributes ; const visObj = attributes . vis ; if ( ! savedVis ) { throw new Error ( 'No Saved Vis' ) ; } const saveOptions = { confirmOverwrite : false , returnToOrigin : true , isTitleDuplicateConfirmed : true , copyOnSave : false , } ; savedVis . title = title ; savedVis . description = '' ; savedVis . searchSourceFields = visObj ?. data . searchSource ?. getSerializedFields ( ) ; savedVis . savedSearchId = visObj ?. data . savedSearchId ; const serializedVis = ( visObj as unknown as Vis ) . serialize ( ) ; const { params , data } = serializedVis ; savedVis . visState = { title , type : serializedVis . type , params , aggs : data . aggs , } ; if ( visObj ) { savedVis . uiStateJSON = visObj ?. uiState . toString ( ) ; } const { core , plugins } = await this . deps . start ( ) ; const id = await saveVisualization ( savedVis , saveOptions , { savedObjectsClient : core . savedObjects . client , overlays : core . overlays , savedObjectsTagging : plugins . savedObjectsTaggingOss ?. getTaggingApi ( ) , } ) ; if ( ! id || id === '' ) { throw new Error ( i18n . translate ( 'visualizations.savingVisualizationFailed.errorMsg' , { defaultMessage : 'Saving a visualization failed' , } ) ) ; } core . chrome . recentlyAccessed . add ( getFullPath ( id ) , savedVis . title , String ( id ) ) ; return { id } ; } catch ( error ) { throw error ; } } public async checkTitle ( props ) { const savedObjectsClient = await this . deps . start ( ) . core . savedObjects . client ; const overlays = await this . deps . start ( ) . core . overlays ; return checkForDuplicateTitle ( { title : props . newTitle , copyOnSave : false , lastSavedTitle : '' , getEsType : ( ) => this . type , getDisplayName : this . getDisplayName || ( ( ) => this . type ) , } , props . isTitleDuplicateConfirmed , props . onTitleDuplicate , { savedObjectsClient , overlays , } ) ; } public inject ( _state , references ) { const state = _state as unknown as VisualizeInput ; const { type , params } = state . savedVis ?? { } ; if ( type && params ) { injectControlsReferences ( type , params , references ) ; injectTimeSeriesReferences ( type , params , references ) ; } return _state ; } public extract ( _state ) { const state = _state as unknown as VisualizeInput ; const references = [ ] ; if ( state . savedVis ?. data . searchSource ) { const [ , searchSourceReferences ] = extractSearchSourceReferences ( state . savedVis . data . searchSource ) ; references . push ( ... searchSourceReferences ) ; } if ( state . savedVis ?. data . savedSearchId ) { references . push ( { name : 'search_0' , type : 'search' , id : String ( state . savedVis . data . savedSearchId ) , } ) ; } const { type , params } = state . savedVis ?? { } ; if ( type && params ) { extractControlsReferences ( type , params , references , `control_${ state . id }` ) ; extractTimeSeriesReferences ( type , params , references , `metrics_${ state . id }` ) ; } return { state : _state , references } ; } }