1import React, { PureComponent } from 'react';
2import { cloneDeep } from 'lodash';
3import {
4  DataQuery,
5  DataSourceApi,
6  DataSourceJsonData,
7  DataSourcePlugin,
8  DataSourcePluginMeta,
9  DataSourceSettings,
10} from '@grafana/data';
11import { AngularComponent, getAngularLoader } from '@grafana/runtime';
12
13export type GenericDataSourcePlugin = DataSourcePlugin<DataSourceApi<DataQuery, DataSourceJsonData>>;
14
15export interface Props {
16  plugin: GenericDataSourcePlugin;
17  dataSource: DataSourceSettings;
18  dataSourceMeta: DataSourcePluginMeta;
19  onModelChange: (dataSource: DataSourceSettings) => void;
20}
21
22export class PluginSettings extends PureComponent<Props> {
23  element: HTMLDivElement | null = null;
24  component?: AngularComponent;
25  scopeProps: {
26    ctrl: { datasourceMeta: DataSourcePluginMeta; current: DataSourceSettings };
27    onModelChanged: (dataSource: DataSourceSettings) => void;
28  };
29
30  constructor(props: Props) {
31    super(props);
32
33    this.scopeProps = {
34      ctrl: { datasourceMeta: props.dataSourceMeta, current: cloneDeep(props.dataSource) },
35      onModelChanged: this.onModelChanged,
36    };
37    this.onModelChanged = this.onModelChanged.bind(this);
38  }
39
40  componentDidMount() {
41    const { plugin } = this.props;
42
43    if (!this.element) {
44      return;
45    }
46
47    if (!plugin.components.ConfigEditor) {
48      // React editor is not specified, let's render angular editor
49      // How to approach this better? Introduce ReactDataSourcePlugin interface and typeguard it here?
50      const loader = getAngularLoader();
51      const template = '<plugin-component type="datasource-config-ctrl" />';
52
53      this.component = loader.load(this.element, this.scopeProps, template);
54    }
55  }
56
57  componentDidUpdate(prevProps: Props) {
58    const { plugin } = this.props;
59    if (!plugin.components.ConfigEditor && this.props.dataSource !== prevProps.dataSource) {
60      this.scopeProps.ctrl.current = cloneDeep(this.props.dataSource);
61
62      this.component?.digest();
63    }
64  }
65
66  componentWillUnmount() {
67    if (this.component) {
68      this.component.destroy();
69    }
70  }
71
72  onModelChanged = (dataSource: DataSourceSettings) => {
73    this.props.onModelChange(dataSource);
74  };
75
76  render() {
77    const { plugin, dataSource } = this.props;
78
79    if (!plugin) {
80      return null;
81    }
82
83    return (
84      <div ref={(element) => (this.element = element)}>
85        {plugin.components.ConfigEditor &&
86          React.createElement(plugin.components.ConfigEditor, {
87            options: dataSource,
88            onOptionsChange: this.onModelChanged,
89          })}
90      </div>
91    );
92  }
93}
94
95export default PluginSettings;
96