Data source config: Add extension point for components (#110588)
* Data source config: Add extension point for components * Fix lint * Fix and add tests * Refactor allowed plugins * Fix lint
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { PluginExtensionTypes, PluginExtensionLink } from '@grafana/data';
|
||||
import { setPluginLinksHook, UsePluginLinksOptions } from '@grafana/runtime';
|
||||
import {
|
||||
PluginExtensionTypes,
|
||||
PluginExtensionLink,
|
||||
ComponentTypeWithExtensionMeta,
|
||||
PluginExtensionDataSourceConfigStatusContext,
|
||||
} from '@grafana/data';
|
||||
import { setPluginLinksHook, UsePluginLinksOptions, setPluginComponentsHook } from '@grafana/runtime';
|
||||
|
||||
import { getMockDataSource } from '../mocks/dataSourcesMocks';
|
||||
|
||||
@@ -15,6 +20,7 @@ jest.mock('../../../core/core', () => ({
|
||||
}));
|
||||
|
||||
setPluginLinksHook(() => ({ links: [], isLoading: false }));
|
||||
setPluginComponentsHook(() => ({ components: [], isLoading: false }));
|
||||
|
||||
const getProps = (partialProps?: Partial<Props>): Props => ({
|
||||
testingStatus: {
|
||||
@@ -140,6 +146,7 @@ describe('<DataSourceTestingStatus />', () => {
|
||||
afterEach(() => {
|
||||
// Reset the hook to default empty state
|
||||
setPluginLinksHook(() => ({ links: [], isLoading: false }));
|
||||
setPluginComponentsHook(() => ({ components: [], isLoading: false }));
|
||||
});
|
||||
|
||||
it('should render plugin links when severity is error and links exist', () => {
|
||||
@@ -271,4 +278,60 @@ describe('<DataSourceTestingStatus />', () => {
|
||||
expect(screen.queryByText('Help Documentation')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Plugin components', () => {
|
||||
const createMockComponent = (
|
||||
overrides: Partial<{
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
pluginId: string;
|
||||
text: string;
|
||||
}> = {}
|
||||
) => {
|
||||
const text = overrides.text ?? 'Test Component';
|
||||
const Comp = ((_props: PluginExtensionDataSourceConfigStatusContext) => (
|
||||
<div>{text}</div>
|
||||
)) as ComponentTypeWithExtensionMeta<PluginExtensionDataSourceConfigStatusContext>;
|
||||
Object.assign(Comp, {
|
||||
meta: {
|
||||
id: overrides.id ?? 'test-component',
|
||||
type: PluginExtensionTypes.component,
|
||||
title: overrides.title ?? 'Test Component',
|
||||
description: overrides.description ?? 'Test component description',
|
||||
pluginId: overrides.pluginId ?? 'grafana-monitoring-app',
|
||||
},
|
||||
});
|
||||
|
||||
return Comp;
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
setPluginComponentsHook(() => ({ components: [], isLoading: false }));
|
||||
});
|
||||
|
||||
it('should render plugin component from allowed plugin', () => {
|
||||
const AllowedComponent = createMockComponent({
|
||||
pluginId: 'grafana-monitoring-app',
|
||||
text: 'Allowed Component',
|
||||
}) as unknown as ComponentTypeWithExtensionMeta<{}>;
|
||||
setPluginComponentsHook(() => ({ components: [AllowedComponent], isLoading: false }));
|
||||
|
||||
render(<DataSourceTestingStatus {...getProps()} />);
|
||||
|
||||
expect(screen.getByText('Allowed Component')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT render plugin component from non-allowed plugin', () => {
|
||||
const BlockedComponent = createMockComponent({
|
||||
pluginId: 'not-allowed-plugin',
|
||||
text: 'Blocked Component',
|
||||
}) as unknown as ComponentTypeWithExtensionMeta<{}>;
|
||||
setPluginComponentsHook(() => ({ components: [BlockedComponent], isLoading: false }));
|
||||
|
||||
render(<DataSourceTestingStatus {...getProps()} />);
|
||||
|
||||
expect(screen.queryByText('Blocked Component')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,11 +6,12 @@ import {
|
||||
GrafanaTheme2,
|
||||
PluginExtensionPoints,
|
||||
PluginExtensionLink,
|
||||
PluginExtensionDataSourceConfigStatusContext,
|
||||
} from '@grafana/data';
|
||||
import { sanitizeUrl } from '@grafana/data/internal';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { TestingStatus, config, usePluginLinks } from '@grafana/runtime';
|
||||
import { TestingStatus, config, usePluginLinks, usePluginComponents, renderLimitedComponents } from '@grafana/runtime';
|
||||
import { AlertVariant, Alert, useTheme2, Link, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { contextSrv } from '../../../core/core';
|
||||
@@ -161,21 +162,28 @@ export function DataSourceTestingStatus({ testingStatus, exploreUrl, dataSource
|
||||
};
|
||||
const styles = useStyles2(getTestingStatusStyles);
|
||||
|
||||
// Extensions context
|
||||
const extensionStatusContext: PluginExtensionDataSourceConfigStatusContext = {
|
||||
dataSource: {
|
||||
type: dataSource.type,
|
||||
uid: dataSource.uid,
|
||||
name: dataSource.name,
|
||||
typeName: dataSource.typeName,
|
||||
},
|
||||
testingStatus,
|
||||
severity,
|
||||
};
|
||||
|
||||
const { links: allStatusLinks } = usePluginLinks({
|
||||
extensionPointId: PluginExtensionPoints.DataSourceConfigStatus,
|
||||
context: {
|
||||
dataSource: {
|
||||
type: dataSource.type,
|
||||
uid: dataSource.uid,
|
||||
name: dataSource.name,
|
||||
typeName: dataSource.typeName,
|
||||
},
|
||||
testingStatus,
|
||||
severity,
|
||||
},
|
||||
context: extensionStatusContext,
|
||||
limitPerPlugin: 1,
|
||||
});
|
||||
|
||||
const { components: extensionComponents } = usePluginComponents<PluginExtensionDataSourceConfigStatusContext>({
|
||||
extensionPointId: PluginExtensionPoints.DataSourceConfigStatus,
|
||||
});
|
||||
|
||||
// Existing error-specific extensions (backward compatibility)
|
||||
const { links: allErrorLinks } = usePluginLinks({
|
||||
extensionPointId: PluginExtensionPoints.DataSourceConfigErrorStatus,
|
||||
@@ -235,6 +243,16 @@ export function DataSourceTestingStatus({ testingStatus, exploreUrl, dataSource
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{extensionComponents.length > 0 && (
|
||||
<div className={styles.linksContainer}>
|
||||
{renderLimitedComponents<PluginExtensionDataSourceConfigStatusContext>({
|
||||
props: extensionStatusContext,
|
||||
components: extensionComponents,
|
||||
limit: 2,
|
||||
pluginId: ALLOWED_DATASOURCE_EXTENSION_PLUGINS,
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,4 +28,5 @@ export const ALLOWED_DATASOURCE_EXTENSION_PLUGINS = [
|
||||
'grafana-pyroscope-app',
|
||||
'grafana-monitoring-app',
|
||||
'grafana-troubleshooting-app',
|
||||
'grafana-assistant-app',
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user