Dashboard Scene: Improve Angular experience (#92847)
In Dashboard Scene: * Display a warning in the panel header when rendering an angular panel * Display a warning in the dashboard when rendering one or more angular panels * Display a button to migrate in the dashboard banner * Display a button to migrate in the panel editor * Display a button to "Edit options" when it is an Angular panel, to open the panel JSON inspector to be able to edit the options * Add tests --------- Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
co-authored by
Torkel Ödegaard
parent
6c91b65aca
commit
5cc11bd1a1
@@ -8,8 +8,10 @@ import {
|
||||
isStandardFieldProp,
|
||||
PanelPluginMeta,
|
||||
restoreCustomOverrideRules,
|
||||
PluginType,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, locationService } from '@grafana/runtime';
|
||||
import {
|
||||
DeepPartial,
|
||||
SceneComponentProps,
|
||||
@@ -19,11 +21,15 @@ import {
|
||||
VizPanel,
|
||||
sceneGraph,
|
||||
} from '@grafana/scenes';
|
||||
import { FilterInput, Stack, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
import { Button, Card, FilterInput, Stack, ToolbarButton, useStyles2 } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { OptionFilter } from 'app/features/dashboard/components/PanelEditor/OptionsPaneOptions';
|
||||
import { getPanelPluginNotFound } from 'app/features/panel/components/PanelPluginError';
|
||||
import { VizTypeChangeDetails } from 'app/features/panel/components/VizTypePicker/types';
|
||||
import { getAllPanelPluginMeta } from 'app/features/panel/state/util';
|
||||
import { AngularDeprecationPluginNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice';
|
||||
|
||||
import { isUsingAngularPanelPlugin } from '../scene/angular/AngularDeprecation';
|
||||
|
||||
import { PanelOptions } from './PanelOptions';
|
||||
import { PanelVizTypePicker } from './PanelVizTypePicker';
|
||||
@@ -82,13 +88,20 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
this.setState({ listMode });
|
||||
};
|
||||
|
||||
onOpenPanelJSON = (vizPanel: VizPanel) => {
|
||||
locationService.partial({
|
||||
inspect: vizPanel.state.key,
|
||||
inspectTab: 'json',
|
||||
});
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<PanelOptionsPane>) => {
|
||||
const { isVizPickerOpen, searchQuery, listMode, panelRef } = model.useState();
|
||||
const panel = panelRef.resolve();
|
||||
const { pluginId } = panel.useState();
|
||||
const { data } = sceneGraph.getData(panel).useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const isAngularPanel = isUsingAngularPanelPlugin(panel);
|
||||
return (
|
||||
<>
|
||||
{!isVizPickerOpen && (
|
||||
@@ -102,6 +115,39 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
onChange={model.onSetSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
{isAngularPanel && (
|
||||
<div className={styles.angularDeprecationContainer}>
|
||||
<AngularDeprecationPluginNotice
|
||||
showPluginDetailsLink={true}
|
||||
pluginId={pluginId}
|
||||
pluginType={PluginType.panel}
|
||||
angularSupportEnabled={config?.angularSupportEnabled}
|
||||
interactionElementId="panel-options"
|
||||
>
|
||||
<Card.Heading>
|
||||
<Trans i18nKey="dashboards.panel-edit.angular-deprecation-heading">Panel options</Trans>
|
||||
</Card.Heading>
|
||||
<Card.Description>
|
||||
<Trans i18nKey="dashboards.panel-edit.angular-deprecation-description">
|
||||
Angular panels options can only be edited using the JSON editor.
|
||||
</Trans>
|
||||
</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button
|
||||
variant="secondary"
|
||||
fullWidth={false}
|
||||
onClick={() => {
|
||||
model.onOpenPanelJSON(panel);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="dashboards.panel-edit.angular-deprecation-button-open-panel-json">
|
||||
Open JSON editor
|
||||
</Trans>
|
||||
</Button>
|
||||
</Card.Actions>
|
||||
</AngularDeprecationPluginNotice>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.listOfOptions}>
|
||||
<PanelOptions panel={panel} searchQuery={searchQuery} listMode={listMode} data={data} />
|
||||
</div>
|
||||
@@ -146,6 +192,13 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
rotateIcon: css({
|
||||
rotate: '180deg',
|
||||
}),
|
||||
angularDeprecationContainer: css({
|
||||
label: 'angular-deprecation-container',
|
||||
padding: theme.spacing(1),
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,17 @@ jest.mock('@grafana/runtime', () => ({
|
||||
getInstanceSettings: jest.fn().mockResolvedValue({ uid: 'ds1' }),
|
||||
};
|
||||
},
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
angularSupportEnabled: true,
|
||||
panels: {
|
||||
'briangann-datatable-panel': {
|
||||
id: 'briangann-datatable-panel',
|
||||
state: 'deprecated',
|
||||
angular: { detected: true, hideDeprecation: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('app/features/playlist/PlaylistSrv', () => ({
|
||||
@@ -826,6 +837,75 @@ describe('DashboardScene', () => {
|
||||
expect(restoredGrid.state.grid.state.children.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When a dashboard contain angular panels', () => {
|
||||
it('should return true if the dashboard contains angular panels', () => {
|
||||
// create a scene with angular panels inside
|
||||
const scene = buildTestScene({
|
||||
body: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-1',
|
||||
x: 0,
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-1',
|
||||
pluginId: 'briangann-datatable-panel',
|
||||
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
|
||||
}),
|
||||
}),
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-2',
|
||||
body: new VizPanel({
|
||||
title: 'Panel B',
|
||||
key: 'panel-2',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
scene.activate();
|
||||
|
||||
expect(scene.hasDashboardAngularPlugins()).toBe(true);
|
||||
});
|
||||
it('should return true if the dashboard contains explicitControllerMigration panels', () => {
|
||||
// create a scene with angular panels inside
|
||||
const scene = buildTestScene({
|
||||
body: new DefaultGridLayoutManager({
|
||||
grid: new SceneGridLayout({
|
||||
children: [
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-1',
|
||||
x: 0,
|
||||
body: new VizPanel({
|
||||
title: 'Panel A',
|
||||
key: 'panel-1',
|
||||
pluginId: 'graph',
|
||||
$data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }),
|
||||
}),
|
||||
}),
|
||||
new DashboardGridItem({
|
||||
key: 'griditem-2',
|
||||
body: new VizPanel({
|
||||
title: 'Panel B',
|
||||
key: 'panel-2',
|
||||
pluginId: 'table',
|
||||
}),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
scene.activate();
|
||||
|
||||
expect(scene.hasDashboardAngularPlugins()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function buildTestScene(overrides?: Partial<DashboardSceneState>) {
|
||||
|
||||
@@ -67,6 +67,7 @@ import { DashboardSceneUrlSync } from './DashboardSceneUrlSync';
|
||||
import { LibraryPanelBehavior } from './LibraryPanelBehavior';
|
||||
import { RowRepeaterBehavior } from './RowRepeaterBehavior';
|
||||
import { ViewPanelScene } from './ViewPanelScene';
|
||||
import { isUsingAngularDatasourcePlugin, isUsingAngularPanelPlugin } from './angular/AngularDeprecation';
|
||||
import { setupKeyboardShortcuts } from './keyboardShortcuts';
|
||||
import { DefaultGridLayoutManager } from './layout-default/DefaultGridLayoutManager';
|
||||
import { DashboardLayoutManager } from './types';
|
||||
@@ -662,6 +663,27 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
locationService.replace('/');
|
||||
}
|
||||
|
||||
public getDashboardPanels() {
|
||||
return dashboardSceneGraph.getVizPanels(this);
|
||||
}
|
||||
|
||||
public hasDashboardAngularPlugins() {
|
||||
const sceneGridLayout = this.state.body;
|
||||
if (!(sceneGridLayout instanceof DefaultGridLayoutManager)) {
|
||||
return false;
|
||||
}
|
||||
const gridItems = sceneGridLayout.state.grid.state.children;
|
||||
const dashboardWasAngular = gridItems.some((gridItem) => {
|
||||
if (!(gridItem instanceof DashboardGridItem)) {
|
||||
return false;
|
||||
}
|
||||
const isAngularPanel = isUsingAngularPanelPlugin(gridItem.state.body);
|
||||
const isAngularDs = isUsingAngularDatasourcePlugin(gridItem.state.body);
|
||||
return isAngularPanel || isAngularDs;
|
||||
});
|
||||
return dashboardWasAngular;
|
||||
}
|
||||
|
||||
public onSetScrollRef = (scrollElement: ScrollRefElement): void => {
|
||||
this._scrollRef = scrollElement;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,36 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import { render } from 'test/test-utils';
|
||||
|
||||
import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, setPluginImportUtils } from '@grafana/runtime';
|
||||
|
||||
import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene';
|
||||
|
||||
setPluginImportUtils({
|
||||
importPanelPlugin: (id: string) => Promise.resolve(getPanelPlugin({})),
|
||||
getPanelPluginFromCache: (id: string) => undefined,
|
||||
});
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
useChromeHeaderHeight: jest.fn(),
|
||||
getDataSourceSrv: () => {
|
||||
return {
|
||||
getInstanceSettings: jest.fn().mockResolvedValue({ uid: 'ds1' }),
|
||||
};
|
||||
},
|
||||
config: {
|
||||
...jest.requireActual('@grafana/runtime').config,
|
||||
angularSupportEnabled: true,
|
||||
panels: {
|
||||
'briangann-datatable-panel': {
|
||||
id: 'briangann-datatable-panel',
|
||||
state: 'deprecated',
|
||||
angular: { detected: true, hideDeprecation: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DashboardSceneRenderer', () => {
|
||||
@@ -50,4 +73,54 @@ describe('DashboardSceneRenderer', () => {
|
||||
|
||||
expect(await screen.findByTestId(selectors.components.EntityNotFound.container)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render angular deprecation notice when dashboard contains angular components', async () => {
|
||||
const noticeText = /This dashboard depends on Angular/i;
|
||||
//enable feature flag angularDeprecationUI
|
||||
config.featureToggles.angularDeprecationUI = true;
|
||||
const scene = transformSaveModelToScene({
|
||||
meta: {},
|
||||
dashboard: {
|
||||
title: 'Angular dashboard',
|
||||
uid: 'uid',
|
||||
schemaVersion: 0,
|
||||
// Disabling build in annotations to avoid mocking Grafana data source
|
||||
annotations: {
|
||||
list: [
|
||||
{
|
||||
builtIn: 1,
|
||||
datasource: {
|
||||
type: 'grafana',
|
||||
uid: '-- Grafana --',
|
||||
},
|
||||
enable: false,
|
||||
hide: true,
|
||||
iconColor: 'rgba(0, 211, 255, 1)',
|
||||
name: 'Annotations & Alerts',
|
||||
type: 'dashboard',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
panels: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'briangann-datatable-panel',
|
||||
gridPos: { x: 0, y: 0, w: 12, h: 6 },
|
||||
title: 'Angular component',
|
||||
options: {
|
||||
showHeader: true,
|
||||
},
|
||||
fieldConfig: { defaults: {}, overrides: [] },
|
||||
datasource: { uid: 'abcdef' },
|
||||
targets: [{ refId: 'A' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<scene.Component model={scene} />);
|
||||
|
||||
expect(await screen.findByText(noticeText)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useSelector } from 'app/types';
|
||||
import { DashboardScene } from './DashboardScene';
|
||||
import { NavToolbarActions } from './NavToolbarActions';
|
||||
import { PanelSearchLayout } from './PanelSearchLayout';
|
||||
import { DashboardAngularDeprecationBanner } from './angular/DashboardAngularDeprecationBanner';
|
||||
|
||||
export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardScene>) {
|
||||
const { controls, overlay, editview, editPanel, isEmpty, meta, viewPanelScene, panelSearch, panelsPerRow } =
|
||||
@@ -65,7 +66,9 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps<DashboardS
|
||||
|
||||
const notFound = meta.dashboardNotFound && <EntityNotFound entity="Dashboard" key="dashboard-not-found" />;
|
||||
|
||||
let body: React.ReactNode = [withPanels];
|
||||
const angularBanner = <DashboardAngularDeprecationBanner dashboard={model} key="angular-deprecation-banner" />;
|
||||
|
||||
let body: React.ReactNode = [angularBanner, withPanels];
|
||||
|
||||
if (notFound) {
|
||||
body = [notFound];
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { SceneComponentProps, SceneObjectBase, VizPanel } from '@grafana/scenes';
|
||||
import { Icon, PanelChrome, Tooltip, useStyles2 } from '@grafana/ui';
|
||||
import { explicitlyControlledMigrationPanels } from 'app/features/dashboard/state/PanelModel';
|
||||
import { isAngularDatasourcePluginAndNotHidden } from 'app/features/plugins/angularDeprecation/utils';
|
||||
|
||||
import { getQueryRunnerFor } from '../../utils/utils';
|
||||
|
||||
export class AngularDeprecation extends SceneObjectBase {
|
||||
static Component = AngularDeprecationRenderer;
|
||||
|
||||
constructor() {
|
||||
super({});
|
||||
this.addActivationHandler(this.onActivate);
|
||||
}
|
||||
|
||||
private onActivate = () => {
|
||||
const panel = this.parent;
|
||||
if (!panel || !(panel instanceof VizPanel)) {
|
||||
throw new Error('PanelNotices can be used only as title items for VizPanel');
|
||||
}
|
||||
};
|
||||
|
||||
public getPanel() {
|
||||
const panel = this.parent;
|
||||
|
||||
if (panel && panel instanceof VizPanel) {
|
||||
return panel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function AngularDeprecationRenderer({ model }: SceneComponentProps<AngularDeprecation>) {
|
||||
const panel = model.getPanel();
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
if (!panel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showAngularNotice = shouldShowAngularNotice(panel);
|
||||
if (showAngularNotice) {
|
||||
const pluginTypeNotice = getPluginTypeNotice(
|
||||
isUsingAngularDatasourcePlugin(panel),
|
||||
isUsingAngularPanelPlugin(panel)
|
||||
);
|
||||
const message = `This ${pluginTypeNotice} requires Angular (deprecated).`;
|
||||
const angularNoticeTooltip = (
|
||||
<Tooltip content={message}>
|
||||
<PanelChrome.TitleItem className={styles.angularNotice} data-testid="angular-deprecation-icon">
|
||||
<Icon name="exclamation-triangle" size="md" />
|
||||
</PanelChrome.TitleItem>
|
||||
</Tooltip>
|
||||
);
|
||||
return angularNoticeTooltip;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getPluginTypeNotice(isAngularDatasource: boolean, isAngularPanel: boolean) {
|
||||
if (isAngularPanel) {
|
||||
return 'panel';
|
||||
}
|
||||
if (isAngularDatasource) {
|
||||
return 'data source';
|
||||
}
|
||||
return 'panel or data source';
|
||||
}
|
||||
|
||||
export function isUsingAngularPanelPlugin(panel: VizPanel) {
|
||||
return (
|
||||
(config.panels[panel.state.pluginId]?.angular?.detected ||
|
||||
explicitlyControlledMigrationPanels.includes(panel.state.pluginId)) &&
|
||||
!config.panels[panel.state.pluginId]?.angular?.hideDeprecation
|
||||
);
|
||||
}
|
||||
|
||||
export function isUsingAngularDatasourcePlugin(panel: VizPanel) {
|
||||
const queryRunner = getQueryRunnerFor(panel);
|
||||
const datasource = queryRunner?.state.datasource;
|
||||
|
||||
return datasource?.uid ? isAngularDatasourcePluginAndNotHidden(datasource?.uid) : false;
|
||||
}
|
||||
|
||||
export function shouldShowAngularNotice(panel: VizPanel) {
|
||||
return (
|
||||
(config.featureToggles.angularDeprecationUI ?? false) &&
|
||||
(isUsingAngularDatasourcePlugin(panel) || isUsingAngularPanelPlugin(panel))
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
angularNotice: css({
|
||||
color: theme.colors.warning.text,
|
||||
}),
|
||||
};
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { VizPanel } from '@grafana/scenes';
|
||||
import { explicitlyControlledMigrationPanels } from 'app/features/dashboard/state/PanelModel';
|
||||
import { AngularDeprecationNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationNotice';
|
||||
|
||||
import { DashboardScene } from '../DashboardScene';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardScene;
|
||||
}
|
||||
|
||||
export const DashboardAngularDeprecationBanner = ({ dashboard }: Props) => {
|
||||
const panels = dashboard.getDashboardPanels();
|
||||
const shouldShowAutoMigrateLink = panels.some((panel) => {
|
||||
if (panel instanceof VizPanel) {
|
||||
return explicitlyControlledMigrationPanels.includes(panel.state.pluginId);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const isContainingAngularPanels =
|
||||
config.featureToggles.angularDeprecationUI && dashboard.hasDashboardAngularPlugins();
|
||||
|
||||
return isContainingAngularPanels && dashboard.state.uid ? (
|
||||
<AngularDeprecationNotice
|
||||
dashboardUid={dashboard.state.uid}
|
||||
showAutoMigrateLink={shouldShowAutoMigrateLink}
|
||||
key={dashboard.state.uid}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
@@ -18,7 +18,6 @@ export function getAngularPanelMigrationHandler(oldModel: PanelModel) {
|
||||
const wasAngular = autoMigrateAngular[oldModel.autoMigrateFrom] != null;
|
||||
const oldOptions = oldModel.getOptionsToRemember();
|
||||
const prevPluginId = oldModel.autoMigrateFrom;
|
||||
|
||||
if (plugin.onPanelTypeChanged) {
|
||||
const prevOptions = wasAngular ? { angular: oldOptions } : oldOptions.options;
|
||||
Object.assign(panel.options, plugin.onPanelTypeChanged(panel, prevPluginId, prevOptions, panel.fieldConfig));
|
||||
|
||||
@@ -37,6 +37,7 @@ import { panelLinksBehavior, panelMenuBehavior } from '../scene/PanelMenuBehavio
|
||||
import { PanelNotices } from '../scene/PanelNotices';
|
||||
import { PanelTimeRange } from '../scene/PanelTimeRange';
|
||||
import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
|
||||
import { AngularDeprecation } from '../scene/angular/AngularDeprecation';
|
||||
import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLayoutManager';
|
||||
import { RowActions } from '../scene/row-actions/RowActions';
|
||||
import { setDashboardPanelContext } from '../scene/setDashboardPanelContext';
|
||||
@@ -278,6 +279,9 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem {
|
||||
|
||||
const titleItems: SceneObject[] = [];
|
||||
|
||||
if (config.featureToggles.angularDeprecationUI) {
|
||||
titleItems.push(new AngularDeprecation());
|
||||
}
|
||||
titleItems.push(
|
||||
new VizPanelLinks({
|
||||
rawLinks: panel.links,
|
||||
|
||||
@@ -13,12 +13,10 @@ function getRefreshPicker(scene: DashboardScene) {
|
||||
}
|
||||
|
||||
function getPanelLinks(panel: VizPanel) {
|
||||
if (
|
||||
panel.state.titleItems &&
|
||||
Array.isArray(panel.state.titleItems) &&
|
||||
panel.state.titleItems[0] instanceof VizPanelLinks
|
||||
) {
|
||||
return panel.state.titleItems[0];
|
||||
if (panel.state.titleItems && Array.isArray(panel.state.titleItems)) {
|
||||
// search panel.state.titleItems for VizPanelLinks
|
||||
const panelLink = panel.state.titleItems.find((item) => item instanceof VizPanelLinks);
|
||||
return panelLink ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -7,14 +7,12 @@ import { Alert } from '@grafana/ui';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
|
||||
pluginId?: string;
|
||||
pluginType?: PluginType;
|
||||
|
||||
angularSupportEnabled?: boolean;
|
||||
showPluginDetailsLink?: boolean;
|
||||
|
||||
interactionElementId?: string;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
function deprecationMessage(pluginType?: string, angularSupportEnabled?: boolean): string {
|
||||
@@ -45,7 +43,15 @@ function deprecationMessage(pluginType?: string, angularSupportEnabled?: boolean
|
||||
// An Alert showing information about Angular deprecation notice.
|
||||
// If the plugin does not use Angular (!plugin.angularDetected), it returns null.
|
||||
export function AngularDeprecationPluginNotice(props: Props): React.ReactElement | null {
|
||||
const { className, angularSupportEnabled, pluginId, pluginType, showPluginDetailsLink, interactionElementId } = props;
|
||||
const {
|
||||
className,
|
||||
angularSupportEnabled,
|
||||
pluginId,
|
||||
pluginType,
|
||||
showPluginDetailsLink,
|
||||
interactionElementId,
|
||||
children,
|
||||
} = props;
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
const interactionAttributes: Record<string, string> = {};
|
||||
@@ -88,6 +94,12 @@ export function AngularDeprecationPluginNotice(props: Props): React.ReactElement
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
{children && (
|
||||
<>
|
||||
<hr />
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -751,6 +751,11 @@
|
||||
}
|
||||
},
|
||||
"dashboards": {
|
||||
"panel-edit": {
|
||||
"angular-deprecation-button-open-panel-json": "Open JSON editor",
|
||||
"angular-deprecation-description": "Angular panels options can only be edited using the JSON editor.",
|
||||
"angular-deprecation-heading": "Panel options"
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
|
||||
@@ -751,6 +751,11 @@
|
||||
}
|
||||
},
|
||||
"dashboards": {
|
||||
"panel-edit": {
|
||||
"angular-deprecation-button-open-panel-json": "Øpęʼn ĴŜØŃ ęđįŧőř",
|
||||
"angular-deprecation-description": "Åʼnģūľäř päʼnęľş őpŧįőʼnş čäʼn őʼnľy þę ęđįŧęđ ūşįʼnģ ŧĥę ĴŜØŃ ęđįŧőř.",
|
||||
"angular-deprecation-heading": "Päʼnęľ őpŧįőʼnş"
|
||||
},
|
||||
"settings": {
|
||||
"variables": {
|
||||
"dependencies": {
|
||||
|
||||
Reference in New Issue
Block a user