grafana/public/app/features/plugins/sandbox/sandbox_components.tsx
Esteban Beltran 0b03344baa
Chore: Update to typescript 5 (#76511)
* Update dependency typescript to v5

* Update yarn.lock

* Fix typescript errors

* Update typescript version sdk

* Revert useDescription.ts

* Fix ts errors

* Fix Typescript errors after Symbol.unscopables type change

* Fix colormanipulator errors

* Update packages/grafana-data/src/vector/FunctionalVector.ts

* Fix ts errors in dashboardmigrator

* Fix sandbox component typescript error

* Update yarn

* Update to typescript 5.2

* Fix typescript error

* update typescript/vscode patch/sdk/whatever

* fix ts errors in elasticsearch

* Fix two errors in alerting

* Fix error in dashboard-scene

* Fix errors in dashboard tests

* Fix errors in explore tests

* Fix error in plugins sandbox

* fix error in DashboardQueryRunner

* fix errors in grafana-data

* fix errors in PanelChrome story

* update betterer

* better fix for cloud monitoring

* fix error in reducer tester

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
Co-authored-by: Josh Hunt <joshhunt@users.noreply.github.com>
Co-authored-by: joshhunt <josh@trtr.co>
2023-11-07 13:20:20 +00:00

107 lines
4.1 KiB
TypeScript

import { isFunction } from 'lodash';
import React, { ComponentType, FC } from 'react';
import { GrafanaPlugin, PluginExtensionConfig, PluginMeta, PluginType } from '@grafana/data';
import { SandboxedPluginObject } from './types';
import { isSandboxedPluginObject } from './utils';
/**
* Plugins must render their components inside a div with a `data-plugin-sandbox` attribute
* that has their pluginId as value.
* If they don't they won't work as expected because they won't be able to get DOM elements
* This affect all type of plugins.
*
* One could say this wrapping should occur inside the Panel,Datasource and App clases inside `@grafana/*`
* packages like `@grafana/data` but this is not the case. Even though this code is less future-proof than
* putting it there we have the following cases to cover:
*
* - plugins could start bundling grafana dependencies: thus not getting updates on sandboxing code or worse,
* modifying the code to escape the sandbox
* - we leak sandboxing code outside of the sandbox configuration. This mean some sandboxing leftover could be
* left in non-sandboxed code (e.g. sandbox wrappers showing up even if sandbox is disabled)
*
* The biggest con is that we must maintain this code to keep it up to date with possible additional components and
* classes that plugins could bring.
*
*/
export async function sandboxPluginComponents(
pluginExports: System.Module,
meta: PluginMeta
): Promise<SandboxedPluginObject | System.Module> {
if (!isSandboxedPluginObject(pluginExports)) {
// we should monitor these cases. There should not be any plugins without a plugin export loaded inside the sandbox
return pluginExports;
}
const pluginObject = await Promise.resolve(pluginExports.plugin);
// intentionally not early exit to cover possible future cases
// wrap panel component
if (Reflect.has(pluginObject, 'panel')) {
Reflect.set(pluginObject, 'panel', withSandboxWrapper(Reflect.get(pluginObject, 'panel'), meta));
}
// wrap datasource components
if (Reflect.has(pluginObject, 'components')) {
const components: Record<string, ComponentType> = Reflect.get(pluginObject, 'components');
Object.entries(components).forEach(([key, value]) => {
Reflect.set(components, key, withSandboxWrapper(value, meta));
});
Reflect.set(pluginObject, 'components', components);
}
// wrap app components
if (Reflect.has(pluginObject, 'root')) {
Reflect.set(pluginObject, 'root', withSandboxWrapper(Reflect.get(pluginObject, 'root'), meta));
}
// extension components
if (Reflect.has(pluginObject, 'extensionConfigs')) {
const extensions: PluginExtensionConfig[] = Reflect.get(pluginObject, 'extensionConfigs');
for (const extension of extensions) {
if (Reflect.has(extension, 'component')) {
Reflect.set(extension, 'component', withSandboxWrapper(Reflect.get(extension, 'component'), meta));
}
}
Reflect.set(pluginObject, 'extensionConfigs', extensions);
}
// config pages
if (Reflect.has(pluginObject, 'configPages')) {
const configPages: NonNullable<GrafanaPlugin['configPages']> = Reflect.get(pluginObject, 'configPages') ?? [];
for (const [key, value] of Object.entries(configPages)) {
if (!value.body || !isFunction(value.body)) {
continue;
}
Reflect.set(configPages, key, {
...value,
body: withSandboxWrapper(value.body, meta),
});
}
Reflect.set(pluginObject, 'configPages', configPages);
}
return pluginExports;
}
const withSandboxWrapper = <P extends object>(
WrappedComponent: ComponentType<P>,
pluginMeta: PluginMeta
): React.MemoExoticComponent<FC<P>> => {
const WithWrapper = React.memo((props: P) => {
return (
<div
data-plugin-sandbox={pluginMeta.id}
style={{ height: pluginMeta.type === PluginType.app || pluginMeta.type === PluginType.panel ? '100%' : 'auto' }}
>
<WrappedComponent {...props} />
</div>
);
});
WithWrapper.displayName = `GrafanaSandbox(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return WithWrapper;
};