Grafana react plugins with canvas inside - reactjs

I am trying to write a grafana react plugin with a canvas inside. I created my template with npx #grafana/toolkit plugin:create my-plugin. I ran this template with yarn install, yarn dev --watch. It works.
To test, I removed options in module.ts file like this
import { PanelPlugin } from '#grafana/data';
import { SimplePanel } from './SimplePanel';
export const plugin = new PanelPlugin(SimplePanel);
I modified SimplePanel.tsx file as follow
import React, { PureComponent } from 'react';
import { PanelProps } from '#grafana/data';
export class SimplePanel extends PureComponent<PanelProps> {
componentDidMount() {
console.log(this.refs.canvas)
const ctx = this.refs.canvas.getContext('2d');
console.log(ctx)
}
render() {
return (
<canvas ref="canvas" width={300} height={300}/>
);
}
}
And I got the following error :
Property 'getContext' does not exist on type 'ReactInstance'.
Property 'getContext' does not exist on type 'Component< any, {}, any>'.
How is it possible to add a canvas in grafana react plugin ?
Thanks for answer.

Thanks to MateuszFalkowski's comment, I manage to add a canvas :
import React, { PureComponent } from 'react';
import { PanelProps } from '#grafana/data';
export class SimplePanel extends PureComponent<PanelProps> {
myCanvasRef = React.createRef<HTMLCanvasElement>()
componentDidMount() {
console.log(this.myCanvasRef)
const ctx = this.myCanvasRef.current!.getContext('2d');
console.log(ctx)
}
render() {
return (
<canvas ref={this.myCanvasRef} width={300} height={300}/>
);
}
}

Related

How to test the map reference in a react-leaflet custom child component using Enzyme?

I've created a custom component in react-leaflet by extending MapControl. The component doesn't return anything but adds something to the map object it refers to via the props.
Custom Child Component
import { MapControl, withLeaflet } from "react-leaflet";
import * as L from "leaflet";
class Dashboard extends MapControl<any, any> {
public createLeafletElement(props: any) {}
public addDashboard() {
const dashboard = L.control({ position: "topright" });
dashboard.onAdd = function() {
this._div = L.DomUtil.create("div", "dashboard");
this._div.setAttribute("data-test", "component-dashboard");
return this._div;
};
const { map } = this.props.leaflet;
dashboard.addTo(map);
}
componentDidMount() {
this.addDashboard();
}
render() {
return null;
}
}
export default withLeaflet(Dashboard);
Parent/Map component
import React from "react";
import { Map, TileLayer } from "react-leaflet";
import Dashboard from "./Dashboard";
class MapContainer extends React.Component<any> {
public constructor(props: any) {
super(props);
}
render() {
return (
<div data-test="component-map">
<Map
center={this.props.center}
zoomSnap={this.props.zoomSnap}
zoom={this.props.zoom}
style={this.props.style}
>
<TileLayer
url={this.props.url}
attribution={this.props.attribution}
/>
<Dashboard />
</Map>
</div>
);
}
}
export default MapContainer;
While testing the child component Dashboard, how do I initialise map?
(and then check if it contains the dashboard)
I'm using Jest and Enzyme
For Jest the following example demonstrates how to:
create an instance of react-leaflet Map component
ensure a custom control (Dashboard in your case) is instantiated
Example
import React from "react";
import ReactDOM from "react-dom";
import { renderIntoDocument } from "react-dom/test-utils";
import { Map, TileLayer } from "react-leaflet";
import Dashboard from "./Dashboard";
it("Control test", () => {
class TestComponent extends React.Component {
constructor() {
super();
this.controlRef = React.createRef();
}
getControl() {
return this.controlRef.current.leafletElement;
}
render() {
return (
<Map center={[0,0]} zoom={10}>
<Dashboard ref={this.controlRef} />
</Map>
);
}
}
const component = renderIntoDocument(<TestComponent />);
const control = component.getControl();
expect(control).not.toBeNull();
});

[flow,react]Why can't I use flow to the react project like this?

I think the flow should also be statically checked when referenced in other react components, but not...
Is it wrong with my use of the flow?
// This is the Icon.jsx file code
// #flow
import * as React from 'react';
import { Icon } from 'antd';
type Props = {
type?: string
};
export default class FGIcon extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
render() {
return (
<Icon
{...this.props}
/>
)
}
}
class testFlow extends React.Component<{}> {
render() {
return (
<FGIcon type={222222}/>
)
}
}
I used <FGIcon type={222222}/> under the current file, the flow check is normal.
Cannot create FGIcon element because number [1] is incompatible with string [2] in property type.Flow(InferError)
but,
// This is the Button.jsx file code
// #flow
import * as React from 'react';
import { Button } from 'antd';
import { Icon } from '../';
export default class FGButton extends React.Component<{}> {
render() {
return (
<div>
<Button
{...this.props}
/>
<Icon type={178954666}/>
</div>
)
}
}
By referencing the Icon react component in Button.jsx files, the flow cannot check the type attribute.
Why can't I use flow to the react project like this?

Why do we need to create a base element class while working on UI in reactjs?

import $ from 'jquery';
export class BaseElement {
constructor(){
this.element = null; //jquery Object
}
appendToElement(el) {
this.createElement();
el.append(this.element);
}
createElement(){
let s = getElementString();
this.element = $(s);
}
getElementString(){
throw 'please override getElementString() in BaseElements';
}
}
why do we need to create this base element class for the UI??
You don't need to create any base component for UI. Below is the sample code for creating components.
Class component
import React from 'react';
export class LoginComponent from React.Component {
render() {
return (
//Your UI code comes here
)
}
}
Function Component
import React from 'react';
export default StaticComponent = (props) {
return (
//Your UI code here
)
}
Please refer React Site for more details.

How to import tsx into a SPFx extension

I have just started my first SharePoint project and cannot figure out how to use my React components in my extension. Here are the relevant files.
Navbar.tsx:
import * as React from "react";
export const Navbar = props => <div>Hello world</div>;
ReactSharePointNavbarApplicationCustomizer.tsx:
import { override } from "#microsoft/decorators";
import { Log } from "#microsoft/sp-core-library";
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from "#microsoft/sp-application-base";
import { Dialog } from "#microsoft/sp-dialog";
import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";
import styles from "./AppCustomizer.module.scss";
import { escape } from "#microsoft/sp-lodash-subset";
import * as Components from "./components";
import Navbar = Components.Navbar;
const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";
/**
* If your command set uses the ClientSideComponentProperties JSON input,
* it will be deserialized into the BaseExtension.properties object.
* You can define an interface to describe it.
*/
export interface IReactSharePointNavbarApplicationCustomizerProperties {}
/** A Custom Action which can be run during execution of a Client Side Application */
export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
IReactSharePointNavbarApplicationCustomizerProperties
> {
private _onDispose(): void {
console.log("No place holder.");
}
private _topPlaceholder: PlaceholderContent | undefined;
private _renderPlaceHolders(): void {
if (!this._topPlaceholder) {
this._topPlaceholder = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this._onDispose }
);
if (!this._topPlaceholder) {
return;
}
if (this.properties) {
const Nav = Navbar(null);
if (this._topPlaceholder.domElement) {
this._topPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="ms-bgColor-themeDark ms-fontColor-white ${
styles.top
}">
${Nav}
${Navbar}
<div>Hello</div>
<Navbar/>
</div>
</div>`;
}
}
}
}
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);
// Added to handle possible changes on the existence of placeholders.
this.context.placeholderProvider.changedEvent.add(
this,
this._renderPlaceHolders
);
// Call render method for generating the HTML elements.
this._renderPlaceHolders();
return Promise.resolve<void>();
}
}
components:
export * from "./Navbar";
My goal is to use my react component as a navigation bar, however I cannot manage to combine tsx and ts in this context.
I followed this guide: https://learn.microsoft.com/en-us/sharepoint/dev/spfx/extensions/get-started/using-page-placeholder-with-extensions
Outside of these files, the only modifications I made were to add a components folder, with the component and index you see above.
Please help me solve this challenge.
After working on this for a few hours, I have found the solution. I was coming at this the wrong way, I needed to use ReactDOM to insert my TSX components. Afterward it was normal React development. No need to try to insert elements in some fancy way as I was doing before.
Here is the working code.
ReactSharePointNavbarApplicationCustomizer.ts:
import * as React from "react";
import * as ReactDOM from "react-dom";
import { override } from "#microsoft/decorators";
import { Log } from "#microsoft/sp-core-library";
import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from "#microsoft/sp-application-base";
import { Dialog } from "#microsoft/sp-dialog";
import * as strings from "ReactSharePointNavbarApplicationCustomizerStrings";
import styles from "./AppCustomizer.module.scss";
import { escape } from "#microsoft/sp-lodash-subset";
import Navbar, { INavbarProps } from "./components/Navbar";
const LOG_SOURCE: string = "ReactSharePointNavbarApplicationCustomizer";
export interface IReactSharePointNavbarApplicationCustomizerProperties {}
export default class ReactSharePointNavbarApplicationCustomizer extends BaseApplicationCustomizer<
IReactSharePointNavbarApplicationCustomizerProperties
> {
private _onDispose(): void {}
private onRender(): void {
const header: PlaceholderContent = this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{
onDispose: this._onDispose
}
);
if (!header) {
Log.error(LOG_SOURCE, new Error("Could not find placeholder PageHeader"));
return;
}
const elem: React.ReactElement<INavbarProps> = React.createElement(Navbar);
ReactDOM.render(elem, header.domElement);
}
#override
public onInit(): Promise<void> {
this.onRender();
return Promise.resolve<void>();
}
}
Navbar.tsx:
import * as React from "react";
import styles from "./Navbar.module.scss";
import NavbarItem from "../NavbarItem";
export interface INavbarProps {}
export default class Navbar extends React.Component<INavbarProps> {
constructor(props: INavbarProps) {
super(props);
}
public render(): JSX.Element {
return (
<div className={"ms-bgColor-themeDark ms-fontColor-white " + styles.nav}>
Hello world
</div>
);
}
}
As you can see, the components.ts export file was unnecessary. And I am sure other code may still be useless in these examples.
I found that importing tsx components into other tsx components works like normal React imports. Just import and insert as an element.

How can I use getSVG() with react-highcharts?

I'm trying to get the SVG string for each highchart and log it to the console before I starting working on building out my function.
I think the issue I'm having is with the way that I'm calling getSVG(), but I'm not sure.
I've tried a number of things and can't seem to get it to work.
The documentation isn't very clear about it:
https://github.com/kirjs/react-highcharts
Edit: I found a similar issue here, but still can't get it to work:
https://github.com/kirjs/react-highcharts/issues/186
import React, { Component } from 'react';
import RHC from 'react-highcharts';
import Highcharts from 'highcharts';
import HighchartsExporting from 'highcharts/modules/exporting';
HighchartsExporting(Highcharts);
const ReactHighcharts = RHC.withHighcharts(Highcharts);
class Chart extends Component {
componentDidMount() {
console.log(this.refs[this.props.name].getSVG());
}
render() {
return (
<ReactHighcharts config={this.props.config} ref={this.props.name}/>
);
}
}
export default Chart;
I'm getting this error:
TypeError: this.refs[this.props.name].getSVG is not a function. (In 'this.refs[this.props.name].getSVG()', 'this.refs[this.props.name].getSVG' is undefined)
Follow the below codes to implement in yours:
const ReactHighcharts = require('react-highcharts');
class MyComponent extends React.Component {
componentDidMount() {
let chart = this.refs.chart.getChart();
let svg = chart.getSVG(); // This is your SVG
}
render() {
return <ReactHighcharts config={config} ref="chart"/>;
}
}
If the above code under componentDidMount() didn't work, then try this code:
componentDidUpdate() {
if (this.refs.chart) {
let chart = this.refs.chart.refs.chart;
let html = document.createElement('html');
html.innerHTML = chart.innerHTML;
let svg = html.getElementsByTagName('svg')[0].outerHTML; // This is your SVG
}
}
import React from 'react';
import HighchartsReact from 'highcharts-react-official';
import Highcharts from 'highcharts/highcharts';
import exporting from 'highcharts/modules/exporting';
exporting(Highcharts)
class Chart extends Component {
componentDidMount() {
console.log(this.refs[chartRef].getSVG());
}
render() {
return (
<HighchartsReact
highcharts={Highcharts}
options={...yourOptions}
ref="chartRef"
/>);
}
}
export default Chart;
Here you can access getSVG. just include exporting(Highcharts)

Resources