mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Consistent Angular deprecation messages and tracking on docs link click (#71715)
* reportInteraction when clicking on angular deprecation docs link * Made messages consistent, removed duplicate component * Revert unnecessary changes in PluginDetailsPage.test.tsx * Moved angular deprecation notice to different folder * Fix component names * Dismissable alert
This commit is contained in:
parent
20d7cf34b2
commit
7f4d8de6f5
@ -1,48 +0,0 @@
|
|||||||
import { css } from '@emotion/css';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { GrafanaTheme2, PanelPlugin } from '@grafana/data';
|
|
||||||
import { Alert, useStyles2 } from '@grafana/ui';
|
|
||||||
|
|
||||||
export interface Props {
|
|
||||||
plugin: PanelPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AngularPanelPluginWarning({ plugin }: Props) {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<Alert title="Angular panel plugin" severity="warning">
|
|
||||||
<div className="markdown-html">
|
|
||||||
<p>The selected panel plugin is using deprecated plugin APIs.</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://grafana.com/docs/grafana/latest/developers/angular_deprecation/"
|
|
||||||
className="external-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Read more on Angular deprecation
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={`plugins/${encodeURIComponent(plugin.meta.id)}`} className="external-link">
|
|
||||||
View plugin details
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStyles(theme: GrafanaTheme2) {
|
|
||||||
return {
|
|
||||||
wrapper: css({
|
|
||||||
padding: theme.spacing(1),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
@ -2,12 +2,13 @@ import { css } from '@emotion/css';
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
import { CustomScrollbar, FilterInput, RadioButtonGroup, useStyles2 } from '@grafana/ui';
|
||||||
|
import { AngularDeprecationPluginNotice } from 'app/features/plugins/angularDeprecation/AngularDeprecationPluginNotice';
|
||||||
|
|
||||||
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
|
import { isPanelModelLibraryPanel } from '../../../library-panels/guard';
|
||||||
|
|
||||||
import { AngularPanelOptions } from './AngularPanelOptions';
|
import { AngularPanelOptions } from './AngularPanelOptions';
|
||||||
import { AngularPanelPluginWarning } from './AngularPanelPluginWarning';
|
|
||||||
import { OptionsPaneCategory } from './OptionsPaneCategory';
|
import { OptionsPaneCategory } from './OptionsPaneCategory';
|
||||||
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
|
||||||
import { getFieldOverrideCategories } from './getFieldOverrideElements';
|
import { getFieldOverrideCategories } from './getFieldOverrideElements';
|
||||||
@ -101,7 +102,15 @@ export const OptionsPaneOptions = (props: OptionPaneRenderProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div className={styles.formBox}>
|
<div className={styles.formBox}>
|
||||||
{panel.isAngularPlugin() && <AngularPanelPluginWarning plugin={plugin} />}
|
{panel.isAngularPlugin() && (
|
||||||
|
<AngularDeprecationPluginNotice
|
||||||
|
className={styles.angularDeprecationWrapper}
|
||||||
|
showPluginDetailsLink={true}
|
||||||
|
pluginId={plugin.meta.id}
|
||||||
|
pluginType={plugin.meta.type}
|
||||||
|
angularSupportEnabled={config?.angularSupportEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className={styles.formRow}>
|
<div className={styles.formRow}>
|
||||||
<FilterInput width={0} value={searchQuery} onChange={setSearchQuery} placeholder={'Search options'} />
|
<FilterInput width={0} value={searchQuery} onChange={setSearchQuery} placeholder={'Search options'} />
|
||||||
</div>
|
</div>
|
||||||
@ -205,4 +214,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
border-top: none;
|
border-top: none;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
`,
|
`,
|
||||||
|
angularDeprecationWrapper: css`
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
@ -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 (
|
|
||||||
<Alert severity="warning" title="Angular plugin" className={className}>
|
|
||||||
<p>{deprecationMessage(angularSupportEnabled)}</p>
|
|
||||||
<a
|
|
||||||
href="https://grafana.com/docs/grafana/latest/developers/angular_deprecation/"
|
|
||||||
className="external-link"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Read more about Angular support deprecation.
|
|
||||||
</a>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
@ -35,7 +35,7 @@ jest.mock('../state/hooks', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('PluginDetailsAngularDeprecation', () => {
|
describe('PluginDetailsPage Angular deprecation', () => {
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
@ -9,8 +9,8 @@ import { Layout } from '@grafana/ui/src/components/Layout/Layout';
|
|||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { AppNotificationSeverity } from 'app/types';
|
import { AppNotificationSeverity } from 'app/types';
|
||||||
|
|
||||||
|
import { AngularDeprecationPluginNotice } from '../../angularDeprecation/AngularDeprecationPluginNotice';
|
||||||
import { Loader } from '../components/Loader';
|
import { Loader } from '../components/Loader';
|
||||||
import { PluginDetailsAngularDeprecation } from '../components/PluginDetailsAngularDeprecation';
|
|
||||||
import { PluginDetailsBody } from '../components/PluginDetailsBody';
|
import { PluginDetailsBody } from '../components/PluginDetailsBody';
|
||||||
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
|
import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError';
|
||||||
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
|
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
|
||||||
@ -76,9 +76,12 @@ export function PluginDetailsPage({
|
|||||||
<Page.Contents>
|
<Page.Contents>
|
||||||
<TabContent className={styles.tabContent}>
|
<TabContent className={styles.tabContent}>
|
||||||
{plugin.angularDetected && (
|
{plugin.angularDetected && (
|
||||||
<PluginDetailsAngularDeprecation
|
<AngularDeprecationPluginNotice
|
||||||
className={styles.alert}
|
className={styles.alert}
|
||||||
angularSupportEnabled={config?.angularSupportEnabled}
|
angularSupportEnabled={config?.angularSupportEnabled}
|
||||||
|
pluginId={plugin.id}
|
||||||
|
pluginType={plugin.type}
|
||||||
|
showPluginDetailsLink={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
||||||
|
@ -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(<AngularDeprecationPluginNotice pluginId="test-id" pluginType={test.pluginType} />);
|
||||||
|
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(
|
||||||
|
<AngularDeprecationPluginNotice pluginId="test-id" angularSupportEnabled={test.angularSupportEnabled} />
|
||||||
|
);
|
||||||
|
expect(screen.getByText(test.expected)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays the plugin details link if showPluginDetailsLink is true', () => {
|
||||||
|
render(<AngularDeprecationPluginNotice pluginId="test-id" showPluginDetailsLink={true} />);
|
||||||
|
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(<AngularDeprecationPluginNotice pluginId="test-id" showPluginDetailsLink={false} />);
|
||||||
|
expect(screen.queryByText(/view plugin details/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports interaction when clicking on the link', async () => {
|
||||||
|
render(<AngularDeprecationPluginNotice pluginId="test-id" />);
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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 : (
|
||||||
|
<Alert severity="warning" title="Angular plugin" className={className} onRemove={() => setDismissed(true)}>
|
||||||
|
<p>{deprecationMessage(pluginType, angularSupportEnabled)}</p>
|
||||||
|
<div className="markdown-html">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://grafana.com/docs/grafana/latest/developers/angular_deprecation/"
|
||||||
|
className="external-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
onClick={() => {
|
||||||
|
reportInteraction('angular_deprecation_docs_clicked', {
|
||||||
|
pluginId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Read our deprecation notice and migration advice.
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{showPluginDetailsLink && pluginId ? (
|
||||||
|
<li>
|
||||||
|
<a href={`plugins/${encodeURIComponent(pluginId)}`} className="external-link">
|
||||||
|
View plugin details
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user