Showing visualizaiton in custom plugin kibana v.8.8.0

I want to render visualizations in my plugin using embeddables. Here is my code,
I am stuck in the errors and unable to solve this. :
Uncaught TypeError: Cannot read properties of undefined (reading 'create')
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'create')

import { Embeddable, IContainer, EmbeddableInput, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
 
import { VisualizeEmbeddableFactory } from '../../../src/plugins/visualizations/public/embeddable';
import { VisualizeInput } from  '../../../src/plugins/visualizations/public/index';
export const HELLO_WORLD_EMBEDDABLE = 'HELLO_WORLD_EMBEDDABLE';
 
export class HelloWorldEmbeddable extends Embeddable {
  public readonly type = HELLO_WORLD_EMBEDDABLE;
 
  constructor(initialInput: any, parent:any, private visualizeEmbeddableFactory: VisualizeEmbeddableFactory) {
    super(initialInput, {}, parent);
  }
 
  public render(node: HTMLElement) {
    // Use the VisualizeEmbeddableFactory to create and render a visualization
    const input: VisualizeInput = {
      savedObjectId: '9c2e5a70-5e67-11ee-a009-8b8a031f2175',
      // You can provide additional input properties as needed
    };
 
    const embeddable = this.visualizeEmbeddableFactory.create(input);
    embeddable.render(node);
  }
 
  public reload() {
    // Implement reload behavior if needed
  }
}

And below method neither working nor giving any error:

import { Embeddable, EmbeddableInput, IContainer } from '@kbn/embeddable-plugin/public';
 
export const HELLO_WORLD_EMBEDDABLE = 'HELLO_WORLD_EMBEDDABLE';
 
export class HelloWorldEmbeddable extends Embeddable {
  // The type of this embeddable. This will be used to find the appropriate factory
  // to instantiate this kind of embeddable.
  public readonly type = HELLO_WORLD_EMBEDDABLE;
 
  constructor(initialInput: EmbeddableInput, parent?: IContainer) {
    super(
      initialInput,
      {},
      parent
    );
  }
 
  /**
   * Render yourself at the dom node using whatever framework you like, angular, react, or just plain
   * vanilla js.
   * @param node
   */
  public render(node: HTMLElement) {
    node.innerHTML =
      '<div data-embeddable-type="visualization" data-embeddable-id="9c2e5a70-5e67-11ee-a009-8b8a031f2175">h</div>';
  }
 
  public reload() {}
}
 

And I found another solution too with iframe but that is also not working. It is giving error related to permissions like " You do not have permission to access the requested page inside iframe.

import { Embeddable, EmbeddableInput, IContainer } from '@kbn/embeddable-plugin/public';
 
export const HELLO_WORLD_EMBEDDABLE = 'HELLO_WORLD_EMBEDDABLE';
 
export class HelloWorldEmbeddable extends Embeddable {
  // The type of this embeddable. This will be used to find the appropriate factory
  // to instantiate this kind of embeddable.
  public readonly type = HELLO_WORLD_EMBEDDABLE;
 
  constructor(initialInput: EmbeddableInput, parent?: IContainer) {
    super(
      // Input state is irrelevant to this embeddable, just pass it along.
      initialInput,
      // Initial output state - this embeddable does not do anything with output, so just
      // pass along an empty object.
      {},
      // Optional parent component, this embeddable can optionally be rendered inside a container.
      parent
    );
  }
 
  /**
   * Render yourself at the dom node using whatever framework you like, angular, react, or just plain
   * vanilla js.
   * @param node
   */
  public render(node: HTMLElement) {
    node.innerHTML =
      '<iframe src="/app/visualize#/edit/9c2e5a70-5e67-11ee-a009-8b8a031f2175?_g" height="600" width="800"></iframe>';
  }
 
  /**
   * This is mostly relevant for time based embeddables which need to update data
   * even if EmbeddableInput has not changed at all.
   */
  public reload() {}
}
 

Hi @Amit_Dhiman

let's start from a basic plugin:

plugin.ts

import { Plugin, CoreSetup, AppNavLinkStatus } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { SettingsStart } from '@kbn/core-ui-settings-browser';
import { mount } from './mount';

export interface StartDependencies {
  data: DataPublicPluginStart;
  lens: LensPublicStart;
  settings: SettingsStart;
}

export interface SetupDependencies {
}

export class MyPluginWithViz
  implements Plugin<void, void, SetupDependencies, StartDependencies>
{
  public setup(core: CoreSetup<StartDependencies>, _: SetupDependencies) {
    core.application.register({
      id: 'my_new_plugin',
      title: 'My cool plugin',
      navLinkStatus: AppNavLinkStatus.hidden,
      mount: mount(core),
    });
  }

  public start() {}

  public stop() {}
}

Now add the "mounting" logic for the actual component to render with a Lens embeddable who points to a saved object:

mount.ts

import * as React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';

import type { CoreSetup, AppMountParameters } from '@kbn/core/public';
import type { StartDependencies } from './plugin';

export const mount =
  (coreSetup: CoreSetup<StartDependencies>) =>
  async ({ element, history }: AppMountParameters) => {
    const [core, plugins] = await coreSetup.getStartServices();

    const i18nCore = core.i18n;
    const LensComponent = plugins.lens.EmbeddableComponent;

    const reactElement = (
      <i18nCore.Context>
        <LensComponent
          id="myLens"
          style={{ height: 500 }}
          timeRange={{
            from: 'now-5d',
            to: 'now',
          }}
          savedObjectId="<saved-object-id-here>"
          onLoad={(val) => {
            console.log('Loaded', val);
          }}
        />
      </i18nCore.Context>
    );

    render(reactElement, element);
    return () => unmountComponentAtNode(element);
  };

Thanks @Marco_Liberati for quick response.
But this too didn't worked...

import React, { useEffect, useRef } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import type { KoatAiPluginStart } from './plugin';
 
const LensVisualization = (props: {
  plugins: KoatAiPluginStart;
  id: any;
}) => {
  const elementRef = useRef<null | HTMLElement>(null); // Type annotation for elementRef
  const LensComponent = props.plugins.lens.EmbeddableComponent;
 
  useEffect(() => {
    if (elementRef.current) {
      const reactElement = (
        <LensComponent
          id={props.id}
          style={{ height: 500 }}
          timeRange={{
            from: 'now-5d',
            to: 'now',
          }}
          onLoad={(val) => {
            console.log('Loaded', val);
          }}
        />
      );
 
      render(reactElement, elementRef.current);
 
      return () => unmountComponentAtNode(elementRef.current);
    }
  }, [props.id]);
 
  return <div ref={elementRef} />;
};
 
export default LensVisualization;

Showing error:

reportUnhandledError.js:10 Uncaught TypeError: Cannot read properties of undefined (reading 'map')
    at Embeddable.initializeOutput (embeddable.tsx:852:1)
    at Embeddable.initializeSavedVis (embeddable.tsx:629:1)

Why declaring the LensComponent within a useEffect?

i am new in kibana plugin development .
This is my first Plugin . I am trying to here use LensVisualization
as Componet.
and also shared you my Compont which use for preview Single lens But its only Works With Lens. I nedd this type of compnent to show Single visulization

import React, { useState } from 'react';
import moment from 'moment';
import { ViewMode } from '@kbn/embeddable-plugin/public';
import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import type { FormulaPublicApi, LensEmbeddableInput } from '@kbn/lens-plugin/public';
import type { KoatAiPluginStart } from './plugin';
export const Singlelens = (props: {
    core: CoreStart;
    plugins: KoatAiPluginStart;
    defaultDataView: DataView;
    formula: FormulaPublicApi;
    attributes: any;
    firstdate?: any;
    lastdate?: any;
    references: any;
    filterItem: any;
    searchItem: any;
}) => {
    props.attributes.references = props.references;
    const formattedDate = moment(props.firstdate).format('YYYY-MM-DD');
    const formattedLastDate = moment(props.lastdate).format('YYYY-MM-DD');
    const [isLoading, setIsLoading] = useState(false);
    const [isSaveModalVisible, setIsSaveModalVisible] = useState(false);
    const [time, setTime] = useState({
        from: formattedDate,
        to: formattedLastDate,
    });
    const filters = props.filterItem.map((queryObj: { alias: any; query: any }) => ({
        meta: { alias: queryObj.alias },
        query: queryObj.query,
    }))
    props.attributes.filters = filters;
    if (props.searchItem) {
        const searchFilter = {
            query: {
                query_string: {
                    query: props.searchItem,
                    analyze_wildcard: true,
                    fields: [
                        'text',
                        'author',
                        'body.entities.mention',
                        'content.tag',
                        'publication',
                        'reference',
                    ],
                },
            },
            meta: {
                alias: 'Search Query',
            },
        };
        filters.push(searchFilter);
    }

    const LensComponent = props.plugins.lens.EmbeddableComponent;
    const LensSaveModalComponent = props.plugins.lens.SaveModalComponent;
    const attributes = props.attributes;
    return (
        <>
            <LensComponent
                id=""
                className='custom-lens-grow'
                withDefaultActions
                style={{ height: 450 }}
                timeRange={time}
                attributes={attributes}
                searchSessionId={''}
                filters={filters}
                onLoad={(val) => {
                    setIsLoading(val);
                }}
                onBrushEnd={({ range }) => {
                    setTime({
                        from: new Date(range[0]).toISOString(),
                        to: new Date(range[1]).toISOString(),
                    });
                }}
                onFilter={(_data) => {
                    // Callback for handling filter events if needed
                }}
                onTableRowClick={(_data) => {
                    // Callback for handling table row click events if needed
                }}
                viewMode={ViewMode.VIEW}
                extraActions={[
                    {
                        id: 'testAction',
                        type: 'link',
                        getIconType: () => 'save',
                        async isCompatible(context: ActionExecutionContext<object>): Promise<boolean> {
                            return true;
                        },
                        execute: async (context: ActionExecutionContext<object>) => {
                            alert('I am an extra action');
                            return;
                        },
                        getDisplayName: () => 'Extra action',
                    },
                ]}
            />
            {isSaveModalVisible && (
                <LensSaveModalComponent
                    initialInput={attributes as unknown as LensEmbeddableInput}
                    onSave={() => { }}
                    onClose={() => setIsSaveModalVisible(false)}
                />
            )}
        </>
    );
};



I called my component Like this

<Singlelens key={`${startDate}-${endDate}-${filterItemN}-${SearchEnter}`} core={core} plugins={plugins} defaultDataView={defaultDataView} formula={formula} attributes={getLensData('Sentiment Resolution')} firstdate={startDate}
                lastdate={endDate} references={getLensRef('Sentiment Resolution')} filterItem={queryArray} searchItem={SearchEnter} />

Hey @Marco_Liberati thanks for helping us out. One direct question we have is "Is there a difference in how the plugin.ts and mount.ts work for a lens type vs a visualization type?" because we have some visualization types that are used for vega and markup type visualizations.
Again thanks for your help we have been stuck on this for a while.

Hi @Marco_Liberati I am trying Embeddable and i am facing an error I think this code is not compatible with V8.8.0. can you help to reach out to from this problem?

My Code

import React, { useEffect, useState } from 'react';
import { VisualizeEmbeddable, VisualizeInput } from 'src/plugins/visualizations/public';
import { CoreStart } from '@kbn/core/public';

const EmbedKibanaVisualization = ({ core }: { core: CoreStart }) => {
  const [embeddable, setEmbeddable] = useState<VisualizeEmbeddable | null>(null);
  const [visualizationId, setVisualizationId] = useState<string>(
    'b2099940-5e67-11ee-a009-8b8a031f2175'
  ); // Replace with your specific visualization ID

  useEffect(() => {
    const input: VisualizeInput = {
      timeRange: {
        from: 'now-7d',
        to: 'now',
      },  
      savedObjectId: visualizationId,
    };
    const embeddableFactory =
      core.application.capabilities.visualizations.getEmbeddableFactory.find(
        ({ savedObjectType }) => savedObjectType === 'visualization'
      );
    if (embeddableFactory) {
      const embeddableInstance = embeddableFactory.create({ input });
      setEmbeddable(embeddableInstance);
    }
  }, [core, visualizationId]);

  return (
    <div>
      {embeddable ? (
        // Render the embeddable instance
        <embeddable.EmbeddableRenderer embeddable={embeddable} />
      ) : (
        // Handle the case when the embeddable is still loading or not found
        <p>Loading or Embeddable Not Found</p>
      )}
    </div>
  );
};

export default EmbedKibanaVisualization;

Error

Detected an unhandled Promise rejection.
TypeError: Cannot read properties of undefined (reading 'visualizations')
VegaChart.tsx:17 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'visualizations')

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.