mirror of
https://github.com/grafana/grafana.git
synced 2026-07-29 15:59:50 -05:00
Plugin Extensions: Add prop types to component extensions (#84295)
* feat: make it possible to specify prop types for component extensions * Update packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts * chore: adapted test case * chore: update betterer * feat: update types for configureComponentExtension() * fix: remove type specifics for `configureExtensionComponent` * Update betterer config --------- Co-authored-by: Darren Janeczek <38694490+darrenjaneczek@users.noreply.github.com> Co-authored-by: Darren Janeczek <darren.janeczek@grafana.com>
This commit is contained in:
co-authored by
Darren Janeczek
Darren Janeczek
parent
fbb6ae35e7
commit
155e38edfe
+3
-3
@@ -765,6 +765,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "12"]
|
||||
],
|
||||
"packages/grafana-runtime/src/services/pluginExtensions/getPluginExtensions.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"packages/grafana-runtime/src/utils/DataSourceWithBackend.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
@@ -2947,9 +2950,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/datasources/components/DataSourceTypeCardList.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/features/datasources/components/EditDataSource.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/datasources/components/picker/DataSourceCard.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
|
||||
@@ -111,9 +111,7 @@ export class AppPlugin<T extends KeyValue = KeyValue> extends GrafanaPlugin<AppP
|
||||
return this;
|
||||
}
|
||||
|
||||
configureExtensionComponent<Context extends object>(
|
||||
extension: Omit<PluginExtensionComponentConfig<Context>, 'type'>
|
||||
) {
|
||||
configureExtensionComponent<Props = {}>(extension: Omit<PluginExtensionComponentConfig<Props>, 'type'>) {
|
||||
this._extensionConfigs.push({
|
||||
...extension,
|
||||
type: PluginExtensionTypes.component,
|
||||
|
||||
@@ -32,9 +32,9 @@ export type PluginExtensionLink = PluginExtensionBase & {
|
||||
category?: string;
|
||||
};
|
||||
|
||||
export type PluginExtensionComponent = PluginExtensionBase & {
|
||||
export type PluginExtensionComponent<Props = {}> = PluginExtensionBase & {
|
||||
type: PluginExtensionTypes.component;
|
||||
component: React.ComponentType;
|
||||
component: React.ComponentType<Props>;
|
||||
};
|
||||
|
||||
export type PluginExtension = PluginExtensionLink | PluginExtensionComponent;
|
||||
@@ -77,16 +77,14 @@ export type PluginExtensionLinkConfig<Context extends object = object> = {
|
||||
category?: string;
|
||||
};
|
||||
|
||||
export type PluginExtensionComponentConfig<Context extends object = object> = {
|
||||
export type PluginExtensionComponentConfig<Props = {}> = {
|
||||
type: PluginExtensionTypes.component;
|
||||
title: string;
|
||||
description: string;
|
||||
|
||||
// The React component that will be rendered as the extension
|
||||
// (This component receives the context as a prop when it is rendered. You can just return `null` from the component to hide for certain contexts)
|
||||
component: React.ComponentType<{
|
||||
context?: Context;
|
||||
}>;
|
||||
// (This component receives contextual information as props when it is rendered. You can just return `null` from the component to hide it.)
|
||||
component: React.ComponentType<Props>;
|
||||
|
||||
// The unique identifier of the Extension Point
|
||||
// (Core Grafana extension point ids are available in the `PluginExtensionPoints` enum)
|
||||
|
||||
@@ -41,10 +41,15 @@ export const getPluginLinkExtensions: GetPluginExtensions<PluginExtensionLink> =
|
||||
};
|
||||
};
|
||||
|
||||
export const getPluginComponentExtensions: GetPluginExtensions<PluginExtensionComponent> = (options) => {
|
||||
// This getter doesn't support the `context` option (contextual information can be passed in as component props)
|
||||
export const getPluginComponentExtensions = <Props = {}>(options: {
|
||||
extensionPointId: string;
|
||||
limitPerPlugin?: number;
|
||||
}): { extensions: Array<PluginExtensionComponent<Props>> } => {
|
||||
const { extensions } = getPluginExtensions(options);
|
||||
const componentExtensions = extensions.filter(isPluginExtensionComponent) as Array<PluginExtensionComponent<Props>>;
|
||||
|
||||
return {
|
||||
extensions: extensions.filter(isPluginExtensionComponent),
|
||||
extensions: componentExtensions,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -139,7 +139,9 @@ export function EditDataSourceView({
|
||||
const extensions = useMemo(() => {
|
||||
const allowedPluginIds = ['grafana-pdc-app', 'grafana-auth-app'];
|
||||
const extensionPointId = PluginExtensionPoints.DataSourceConfig;
|
||||
const { extensions } = getPluginComponentExtensions({ extensionPointId });
|
||||
const { extensions } = getPluginComponentExtensions<{
|
||||
context: PluginExtensionDataSourceConfigContext<DataSourceJsonData>;
|
||||
}>({ extensionPointId });
|
||||
|
||||
return extensions.filter((e) => allowedPluginIds.includes(e.pluginId));
|
||||
}, []);
|
||||
@@ -202,9 +204,7 @@ export function EditDataSourceView({
|
||||
|
||||
{/* Extension point */}
|
||||
{extensions.map((extension) => {
|
||||
const Component = extension.component as React.ComponentType<{
|
||||
context: PluginExtensionDataSourceConfigContext<DataSourceJsonData>;
|
||||
}>;
|
||||
const Component = extension.component;
|
||||
|
||||
return (
|
||||
<div key={extension.id}>
|
||||
|
||||
@@ -445,12 +445,18 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
|
||||
describe('wrapExtensionComponentWithContext()', () => {
|
||||
const ExampleComponent = () => {
|
||||
type ExampleComponentProps = {
|
||||
audience?: string;
|
||||
};
|
||||
|
||||
const ExampleComponent = (props: ExampleComponentProps) => {
|
||||
const { meta } = usePluginContext();
|
||||
|
||||
const audience = props.audience || 'Grafana';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello Grafana!</h1> Version: {meta.info.version}
|
||||
<h1>Hello {audience}!</h1> Version: {meta.info.version}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -464,5 +470,15 @@ describe('Plugin Extensions / Utils', () => {
|
||||
expect(await screen.findByText('Hello Grafana!')).toBeVisible();
|
||||
expect(screen.getByText('Version: 1.0.0')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should pass the properties into the wrapped component', async () => {
|
||||
const pluginId = 'grafana-worldmap-panel';
|
||||
const Component = wrapWithPluginContext(pluginId, ExampleComponent);
|
||||
|
||||
render(<Component audience="folks" />);
|
||||
|
||||
expect(await screen.findByText('Hello folks!')).toBeVisible();
|
||||
expect(screen.getByText('Version: 1.0.0')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,6 @@ export function UserProfileEditPage({
|
||||
const extensionComponents = useMemo(() => {
|
||||
const { extensions } = getPluginComponentExtensions({
|
||||
extensionPointId: PluginExtensionPoints.UserProfileTab,
|
||||
context: {},
|
||||
});
|
||||
|
||||
return extensions;
|
||||
|
||||
Reference in New Issue
Block a user