- {panel.isAngularPlugin() &&
}
+ {panel.isAngularPlugin() && (
+
+ )}
@@ -205,4 +214,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
border-top: none;
flex-grow: 1;
`,
+ angularDeprecationWrapper: css`
+ padding: ${theme.spacing(1)};
+ `,
});
diff --git a/public/app/features/plugins/admin/components/PluginDetailsAngularDeprecation.tsx b/public/app/features/plugins/admin/components/PluginDetailsAngularDeprecation.tsx
deleted file mode 100644
index c5e9103a6e4..00000000000
--- a/public/app/features/plugins/admin/components/PluginDetailsAngularDeprecation.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-
-import { Alert } from '@grafana/ui';
-
-type Props = {
- className?: string;
- angularSupportEnabled?: boolean;
-};
-
-function deprecationMessage(angularSupportEnabled?: boolean): string {
- const msg = 'This plugin uses a deprecated, legacy platform based on AngularJS and ';
- if (angularSupportEnabled === undefined) {
- return msg + ' may be incompatible depending on your Grafana configuration.';
- }
- if (angularSupportEnabled) {
- return msg + ' will stop working in future releases of Grafana.';
- }
- return msg + ' is incompatible with your current Grafana configuration.';
-}
-
-// An Alert showing information about Angular deprecation notice.
-// If the plugin does not use Angular (!plugin.angularDetected), it returns null.
-export function PluginDetailsAngularDeprecation({
- className,
- angularSupportEnabled,
-}: Props): React.ReactElement | null {
- return (
-
- {deprecationMessage(angularSupportEnabled)}
-
- Read more about Angular support deprecation.
-
-
- );
-}
diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx
index 912688b2491..6ff3519d6b2 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsPage.test.tsx
@@ -35,7 +35,7 @@ jest.mock('../state/hooks', () => ({
}),
}));
-describe('PluginDetailsAngularDeprecation', () => {
+describe('PluginDetailsPage Angular deprecation', () => {
afterAll(() => {
jest.resetAllMocks();
});
diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
index 0021f64cac4..afa73b9ac72 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx
@@ -9,8 +9,8 @@ import { Layout } from '@grafana/ui/src/components/Layout/Layout';
import { Page } from 'app/core/components/Page/Page';
import { AppNotificationSeverity } from 'app/types';
+import { AngularDeprecationPluginNotice } from '../../angularDeprecation/AngularDeprecationPluginNotice';
import { Loader } from '../components/Loader';
-import { PluginDetailsAngularDeprecation } from '../components/PluginDetailsAngularDeprecation';
import { PluginDetailsBody } from '../components/PluginDetailsBody';
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
@@ -76,9 +76,12 @@ export function PluginDetailsPage({
{plugin.angularDetected && (
-
)}
diff --git a/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.test.tsx b/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.test.tsx
new file mode 100644
index 00000000000..dde31332d20
--- /dev/null
+++ b/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.test.tsx
@@ -0,0 +1,100 @@
+import { screen, render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { PluginType } from '@grafana/data';
+import { reportInteraction } from '@grafana/runtime';
+
+import { AngularDeprecationPluginNotice } from './AngularDeprecationPluginNotice';
+
+jest.mock('@grafana/runtime', () => ({
+ ...jest.requireActual('@grafana/runtime'),
+ reportInteraction: jest.fn(),
+}));
+
+describe('AngularDeprecationPluginNotice', () => {
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('Plugin type message', () => {
+ const tests = [
+ {
+ name: 'undefined (default)',
+ pluginType: undefined,
+ expected: /This plugin uses/i,
+ },
+ {
+ name: 'app',
+ pluginType: PluginType.app,
+ expected: /This app plugin uses/i,
+ },
+ {
+ name: 'panel',
+ pluginType: PluginType.panel,
+ expected: /This panel plugin uses/i,
+ },
+ {
+ name: 'data source',
+ pluginType: PluginType.datasource,
+ expected: /This data source plugin uses/i,
+ },
+ ];
+ tests.forEach((test) => {
+ it(`displays the correct plugin type for ${test.name}`, () => {
+ render();
+ expect(screen.getByText(test.expected)).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('Angular configuration', () => {
+ const tests = [
+ {
+ name: 'undefined (default)',
+ angularSupportEnabled: undefined,
+ expected: /may be incompatible/i,
+ },
+ {
+ name: 'true',
+ angularSupportEnabled: true,
+ expected: /will stop working/i,
+ },
+ {
+ name: 'false',
+ angularSupportEnabled: false,
+ expected: /is incompatible/i,
+ },
+ ];
+ tests.forEach((test) => {
+ it(`displays the correct angular configuration for ${test.name}`, () => {
+ render(
+
+ );
+ expect(screen.getByText(test.expected)).toBeInTheDocument();
+ });
+ });
+ });
+
+ it('displays the plugin details link if showPluginDetailsLink is true', () => {
+ render();
+ const detailsLink = screen.getByText(/view plugin details/i);
+ expect(detailsLink).toBeInTheDocument();
+ expect(detailsLink).toHaveAttribute('href', 'plugins/test-id');
+ });
+
+ it('does not display the plugin details link if showPluginDetailsLink is false', () => {
+ render();
+ expect(screen.queryByText(/view plugin details/i)).not.toBeInTheDocument();
+ });
+
+ it('reports interaction when clicking on the link', async () => {
+ render();
+ const c = 'Read our deprecation notice and migration advice.';
+ expect(screen.getByText(c)).toBeInTheDocument();
+ await userEvent.click(screen.getByText(c));
+ expect(reportInteraction).toHaveBeenCalledWith('angular_deprecation_docs_clicked', {
+ pluginId: 'test-id',
+ });
+ });
+});
diff --git a/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.tsx b/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.tsx
new file mode 100644
index 00000000000..485703cf320
--- /dev/null
+++ b/public/app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice.tsx
@@ -0,0 +1,78 @@
+import React, { useState } from 'react';
+
+import { PluginType } from '@grafana/data';
+import { reportInteraction } from '@grafana/runtime';
+import { Alert } from '@grafana/ui';
+
+type Props = {
+ className?: string;
+
+ pluginId?: string;
+ pluginType?: PluginType;
+
+ angularSupportEnabled?: boolean;
+ showPluginDetailsLink?: boolean;
+};
+
+function deprecationMessage(pluginType?: string, angularSupportEnabled?: boolean): string {
+ let pluginTypeString: string;
+ switch (pluginType) {
+ case PluginType.app:
+ pluginTypeString = 'app plugin';
+ break;
+ case PluginType.panel:
+ pluginTypeString = 'panel plugin';
+ break;
+ case PluginType.datasource:
+ pluginTypeString = 'data source plugin';
+ break;
+ default:
+ pluginTypeString = 'plugin';
+ }
+ let msg = `This ${pluginTypeString} uses a deprecated, legacy platform based on AngularJS and `;
+ if (angularSupportEnabled === undefined) {
+ return msg + ' may be incompatible depending on your Grafana configuration.';
+ }
+ if (angularSupportEnabled) {
+ return msg + ' will stop working in future releases of Grafana.';
+ }
+ return msg + ' is incompatible with your current Grafana configuration.';
+}
+
+// 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 } = props;
+ const [dismissed, setDismissed] = useState(false);
+ return dismissed ? null : (
+ setDismissed(true)}>
+ {deprecationMessage(pluginType, angularSupportEnabled)}
+
+
+ );
+}