import { AnyAction } from '@reduxjs/toolkit'; import { cloneDeep } from 'lodash'; import { useMemo } from 'react'; import * as React from 'react'; import { DataSourcePluginContextProvider, DataSourcePluginMeta, DataSourceSettings as DataSourceSettingsType, PluginExtensionPoints, PluginExtensionDataSourceConfigContext, DataSourceUpdatedSuccessfully, } from '@grafana/data'; import { getDataSourceSrv, usePluginComponents, UsePluginComponentsResult } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { DataSourceSettingsState, useDispatch } from 'app/types'; import { dataSourceLoaded, setDataSourceName, setIsDefault, useDataSource, useDataSourceExploreUrl, useDataSourceMeta, useDataSourceRights, useDataSourceSettings, useDeleteLoadedDataSource, useInitDataSourceSettings, useTestDataSource, useUpdateDatasource, } from '../state'; import { trackDsConfigClicked, trackDsConfigUpdated } from '../tracking'; import { DataSourceRights } from '../types'; import { BasicSettings } from './BasicSettings'; import { ButtonRow } from './ButtonRow'; import { CloudInfoBox } from './CloudInfoBox'; import { DataSourceLoadError } from './DataSourceLoadError'; import { DataSourceMissingRightsMessage } from './DataSourceMissingRightsMessage'; import { DataSourcePluginConfigPage } from './DataSourcePluginConfigPage'; import { DataSourcePluginSettings } from './DataSourcePluginSettings'; import { DataSourcePluginState } from './DataSourcePluginState'; import { DataSourceReadOnlyMessage } from './DataSourceReadOnlyMessage'; import { DataSourceTestingStatus } from './DataSourceTestingStatus'; export type Props = { // The ID of the data source uid: string; // The ID of the custom datasource setting page pageId?: string | null; }; export function EditDataSource({ uid, pageId }: Props) { useInitDataSourceSettings(uid); const dispatch = useDispatch(); const dataSource = useDataSource(uid); const dataSourceMeta = useDataSourceMeta(dataSource.type); const dataSourceSettings = useDataSourceSettings(); const dataSourceRights = useDataSourceRights(uid); const exploreUrl = useDataSourceExploreUrl(uid); const onDelete = useDeleteLoadedDataSource(); const onTest = useTestDataSource(uid); const onUpdate = useUpdateDatasource(); const onDefaultChange = (value: boolean) => dispatch(setIsDefault(value)); const onNameChange = (name: string) => dispatch(setDataSourceName(name)); const onOptionsChange = (ds: DataSourceSettingsType) => dispatch(dataSourceLoaded(ds)); return ( ); } export type ViewProps = { pageId?: string | null; dataSource: DataSourceSettingsType; dataSourceMeta: DataSourcePluginMeta; dataSourceSettings: DataSourceSettingsState; dataSourceRights: DataSourceRights; exploreUrl: string; onDelete: () => void; onDefaultChange: (isDefault: boolean) => AnyAction; onNameChange: (name: string) => AnyAction; onOptionsChange: (dataSource: DataSourceSettingsType) => AnyAction; onTest: () => void; onUpdate: (dataSource: DataSourceSettingsType) => Promise; }; export function EditDataSourceView({ pageId, dataSource, dataSourceMeta, dataSourceSettings, dataSourceRights, exploreUrl, onDelete, onDefaultChange, onNameChange, onOptionsChange, onTest, onUpdate, }: ViewProps) { const { plugin, loadError, testingStatus, loading } = dataSourceSettings; const { readOnly, hasWriteRights, hasDeleteRights } = dataSourceRights; const hasDataSource = dataSource.id > 0; const { components, isLoading } = useDataSourceConfigPluginExtensions(); const dsi = getDataSourceSrv()?.getInstanceSettings(dataSource.uid); const onSubmit = async (e: React.MouseEvent | React.FormEvent) => { e.preventDefault(); trackDsConfigClicked('save_and_test'); try { await onUpdate({ ...dataSource }); trackDsConfigUpdated({ item: 'success' }); appEvents.publish(new DataSourceUpdatedSuccessfully()); } catch (error) { trackDsConfigUpdated({ item: 'fail' }); return; } onTest(); }; if (loadError) { return ( { trackDsConfigClicked('delete'); onDelete(); }} /> ); } if (loading || isLoading) { return ; } // TODO - is this needed? if (!hasDataSource || !dsi) { return null; } if (pageId) { return ( ); } return (
{!hasWriteRights && } {readOnly && } {dataSourceMeta.state && } {plugin && ( )} {/* Extension point */} {components.map((Component) => { return (
onOptionsChange({ ...dataSource, jsonData: { ...dataSource.jsonData, ...jsonData }, }), setSecureJsonData: (secureJsonData) => onOptionsChange({ ...dataSource, secureJsonData: { ...dataSource.secureJsonData, ...secureJsonData }, }), }} />
); })} { trackDsConfigClicked('delete'); onDelete(); }} onTest={() => { trackDsConfigClicked('test'); onTest(); }} canDelete={!readOnly && hasDeleteRights} canSave={!readOnly && hasWriteRights} /> ); } type DataSourceConfigPluginExtensionProps = { context: PluginExtensionDataSourceConfigContext; }; function useDataSourceConfigPluginExtensions(): UsePluginComponentsResult { const { components, isLoading } = usePluginComponents({ extensionPointId: PluginExtensionPoints.DataSourceConfig, }); return useMemo(() => { const allowedComponents = components.filter((component) => { switch (component.meta.pluginId) { case 'grafana-pdc-app': case 'grafana-auth-app': return true; default: return false; } }); return { components: allowedComponents, isLoading }; }, [components, isLoading]); }