mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 20:59:35 +08:00
Catalog: Display badges for Angular plugins and disable install if Angular is disabled (#69084)
* Angular deprecation: Add Angular badge in plugin catalog page * Angular deprecation: Add alert in plugin details page * Angular deprecation: Disable install button if for Angular plugins * removed extra console.log * Add tests for Angular badge * Add tests for PluginDetailsAngularDeprecation * Add tests for InstallControlsButton * Add tests for ExternallyManagedButton * Table tests * Catalog: Update angular deprecation message * PR review feedback * Update tests * Update copy for angular tooltip and alert * Update tests * Fix test warnings * Fix angularDetected not being set for remote catalog plugins * Dynamic alert text based on grafana config * Moved deprecation message to a separate function * Removed unused Props in PluginAngularBadge
This commit is contained in:
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Badge } from '@grafana/ui';
|
||||||
|
|
||||||
|
export function PluginAngularBadge(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
icon="exclamation-triangle"
|
||||||
|
text="Angular"
|
||||||
|
color="orange"
|
||||||
|
tooltip="This plugin uses deprecated functionality, support for which is being removed."
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, PluginType } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { CatalogPlugin } from '../../types';
|
import { CatalogPlugin } from '../../types';
|
||||||
@ -12,15 +12,9 @@ type Props = {
|
|||||||
|
|
||||||
export function PluginUpdateAvailableBadge({ plugin }: Props): React.ReactElement | null {
|
export function PluginUpdateAvailableBadge({ plugin }: Props): React.ReactElement | null {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
// Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
|
|
||||||
if (plugin.hasUpdate && !plugin.isCore && plugin.type !== PluginType.renderer) {
|
|
||||||
return <p className={styles.hasUpdate}>Update available!</p>;
|
return <p className={styles.hasUpdate}>Update available!</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStyles = (theme: GrafanaTheme2) => {
|
export const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
hasUpdate: css`
|
hasUpdate: css`
|
||||||
|
@ -2,3 +2,4 @@ export { PluginDisabledBadge } from './PluginDisabledBadge';
|
|||||||
export { PluginInstalledBadge } from './PluginInstallBadge';
|
export { PluginInstalledBadge } from './PluginInstallBadge';
|
||||||
export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
|
export { PluginEnterpriseBadge } from './PluginEnterpriseBadge';
|
||||||
export { PluginUpdateAvailableBadge } from './PluginUpdateAvailableBadge';
|
export { PluginUpdateAvailableBadge } from './PluginUpdateAvailableBadge';
|
||||||
|
export { PluginAngularBadge } from './PluginAngularBadge';
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
|
import { PluginStatus } from '../../types';
|
||||||
|
|
||||||
|
import { ExternallyManagedButton } from './ExternallyManagedButton';
|
||||||
|
|
||||||
|
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
|
||||||
|
config.angularSupportEnabled = opts.angularSupportEnabled;
|
||||||
|
render(
|
||||||
|
<ExternallyManagedButton
|
||||||
|
pluginId={'some-plugin-id'}
|
||||||
|
angularDetected={opts.angularDetected}
|
||||||
|
pluginStatus={PluginStatus.INSTALL}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ExternallyManagedButton', () => {
|
||||||
|
let oldAngularSupportEnabled = config.angularSupportEnabled;
|
||||||
|
afterAll(() => {
|
||||||
|
config.angularSupportEnabled = oldAngularSupportEnabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([{ angularSupportEnabled: true }, { angularSupportEnabled: false }])(
|
||||||
|
'angular support is $angularSupportEnabled',
|
||||||
|
({ angularSupportEnabled }) => {
|
||||||
|
it.each([
|
||||||
|
{ angularDetected: true, expectEnabled: angularSupportEnabled },
|
||||||
|
{ angularDetected: false, expectEnabled: true },
|
||||||
|
])('angular detected is $angularDetected', ({ angularDetected, expectEnabled }) => {
|
||||||
|
setup({ angularSupportEnabled, angularDetected });
|
||||||
|
|
||||||
|
const el = screen.getByRole('link');
|
||||||
|
expect(el).toHaveTextContent(/install/i);
|
||||||
|
expect(el).toBeVisible();
|
||||||
|
const linkDisabledStyle = 'pointer-events: none';
|
||||||
|
if (expectEnabled) {
|
||||||
|
expect(el).not.toHaveStyle(linkDisabledStyle);
|
||||||
|
} else {
|
||||||
|
expect(el).toHaveStyle(linkDisabledStyle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { HorizontalGroup, LinkButton } from '@grafana/ui';
|
import { HorizontalGroup, LinkButton } from '@grafana/ui';
|
||||||
|
|
||||||
import { getExternalManageLink } from '../../helpers';
|
import { getExternalManageLink } from '../../helpers';
|
||||||
@ -8,9 +9,10 @@ import { PluginStatus } from '../../types';
|
|||||||
type ExternallyManagedButtonProps = {
|
type ExternallyManagedButtonProps = {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
pluginStatus: PluginStatus;
|
pluginStatus: PluginStatus;
|
||||||
|
angularDetected?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ExternallyManagedButton({ pluginId, pluginStatus }: ExternallyManagedButtonProps) {
|
export function ExternallyManagedButton({ pluginId, pluginStatus, angularDetected }: ExternallyManagedButtonProps) {
|
||||||
const externalManageLink = `${getExternalManageLink(pluginId)}/?tab=installation`;
|
const externalManageLink = `${getExternalManageLink(pluginId)}/?tab=installation`;
|
||||||
|
|
||||||
if (pluginStatus === PluginStatus.UPDATE) {
|
if (pluginStatus === PluginStatus.UPDATE) {
|
||||||
@ -35,7 +37,12 @@ export function ExternallyManagedButton({ pluginId, pluginStatus }: ExternallyMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinkButton href={externalManageLink} target="_blank" rel="noopener noreferrer">
|
<LinkButton
|
||||||
|
disabled={!config.angularSupportEnabled && angularDetected}
|
||||||
|
href={externalManageLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
Install via grafana.com
|
Install via grafana.com
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { TestProvider } from 'test/helpers/TestProvider';
|
||||||
|
|
||||||
|
import { PluginSignatureStatus } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
|
||||||
|
import { CatalogPlugin, PluginStatus } from '../../types';
|
||||||
|
|
||||||
|
import { InstallControlsButton } from './InstallControlsButton';
|
||||||
|
|
||||||
|
const plugin: CatalogPlugin = {
|
||||||
|
description: 'The test plugin',
|
||||||
|
downloads: 5,
|
||||||
|
id: 'test-plugin',
|
||||||
|
info: {
|
||||||
|
logos: { small: '', large: '' },
|
||||||
|
},
|
||||||
|
name: 'Testing Plugin',
|
||||||
|
orgName: 'Test',
|
||||||
|
popularity: 0,
|
||||||
|
signature: PluginSignatureStatus.valid,
|
||||||
|
publishedAt: '2020-09-01',
|
||||||
|
updatedAt: '2021-06-28',
|
||||||
|
hasUpdate: false,
|
||||||
|
isInstalled: false,
|
||||||
|
isCore: false,
|
||||||
|
isDev: false,
|
||||||
|
isEnterprise: false,
|
||||||
|
isDisabled: false,
|
||||||
|
isPublished: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
function setup(opts: { angularSupportEnabled: boolean; angularDetected: boolean }) {
|
||||||
|
config.angularSupportEnabled = opts.angularSupportEnabled;
|
||||||
|
render(
|
||||||
|
<TestProvider>
|
||||||
|
<InstallControlsButton
|
||||||
|
plugin={{ ...plugin, angularDetected: opts.angularDetected }}
|
||||||
|
pluginStatus={PluginStatus.INSTALL}
|
||||||
|
/>
|
||||||
|
</TestProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('InstallControlsButton', () => {
|
||||||
|
let oldAngularSupportEnabled = config.angularSupportEnabled;
|
||||||
|
afterAll(() => {
|
||||||
|
config.angularSupportEnabled = oldAngularSupportEnabled;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([{ angularSupportEnabled: true }, { angularSupportEnabled: false }])(
|
||||||
|
'angular support is $angularSupportEnabled',
|
||||||
|
({ angularSupportEnabled }) => {
|
||||||
|
it.each([
|
||||||
|
{ angularDetected: true, expectEnabled: angularSupportEnabled },
|
||||||
|
{ angularDetected: false, expectEnabled: true },
|
||||||
|
])('angular detected is $angularDetected', ({ angularDetected, expectEnabled }) => {
|
||||||
|
setup({ angularSupportEnabled, angularDetected });
|
||||||
|
|
||||||
|
const el = screen.getByRole('button');
|
||||||
|
expect(el).toHaveTextContent(/install/i);
|
||||||
|
expect(el).toBeVisible();
|
||||||
|
if (expectEnabled) {
|
||||||
|
expect(el).toBeEnabled();
|
||||||
|
} else {
|
||||||
|
expect(el).toBeDisabled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppEvents } from '@grafana/data';
|
import { AppEvents } from '@grafana/data';
|
||||||
import { locationService } from '@grafana/runtime';
|
import { config, locationService } from '@grafana/runtime';
|
||||||
import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui';
|
import { Button, HorizontalGroup, ConfirmModal } from '@grafana/ui';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
@ -17,7 +17,7 @@ type InstallControlsButtonProps = {
|
|||||||
plugin: CatalogPlugin;
|
plugin: CatalogPlugin;
|
||||||
pluginStatus: PluginStatus;
|
pluginStatus: PluginStatus;
|
||||||
latestCompatibleVersion?: Version;
|
latestCompatibleVersion?: Version;
|
||||||
setNeedReload: (needReload: boolean) => void;
|
setNeedReload?: (needReload: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function InstallControlsButton({
|
export function InstallControlsButton({
|
||||||
@ -58,7 +58,7 @@ export function InstallControlsButton({
|
|||||||
if (!errorInstalling && !('error' in result)) {
|
if (!errorInstalling && !('error' in result)) {
|
||||||
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
|
appEvents.emit(AppEvents.alertSuccess, [`Installed ${plugin.name}`]);
|
||||||
if (plugin.type === 'app') {
|
if (plugin.type === 'app') {
|
||||||
setNeedReload(true);
|
setNeedReload?.(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -77,7 +77,7 @@ export function InstallControlsButton({
|
|||||||
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${plugin.name}`]);
|
appEvents.emit(AppEvents.alertSuccess, [`Uninstalled ${plugin.name}`]);
|
||||||
if (plugin.type === 'app') {
|
if (plugin.type === 'app') {
|
||||||
dispatch(removePluginFromNavTree({ pluginID: plugin.id }));
|
dispatch(removePluginFromNavTree({ pluginID: plugin.id }));
|
||||||
setNeedReload(false);
|
setNeedReload?.(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -122,9 +122,9 @@ export function InstallControlsButton({
|
|||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const shouldDisable = isInstalling || errorInstalling || (!config.angularSupportEnabled && plugin.angularDetected);
|
||||||
return (
|
return (
|
||||||
<Button disabled={isInstalling || errorInstalling} onClick={onInstall}>
|
<Button disabled={shouldDisable} onClick={onInstall}>
|
||||||
{isInstalling ? 'Installing' : 'Install'}
|
{isInstalling ? 'Installing' : 'Install'}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
@ -42,7 +42,11 @@ export const PluginActions = ({ plugin }: Props) => {
|
|||||||
{!isInstallControlsDisabled && (
|
{!isInstallControlsDisabled && (
|
||||||
<>
|
<>
|
||||||
{isExternallyManaged ? (
|
{isExternallyManaged ? (
|
||||||
<ExternallyManagedButton pluginId={plugin.id} pluginStatus={pluginStatus} />
|
<ExternallyManagedButton
|
||||||
|
pluginId={plugin.id}
|
||||||
|
pluginStatus={pluginStatus}
|
||||||
|
angularDetected={plugin.angularDetected}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<InstallControlsButton
|
<InstallControlsButton
|
||||||
plugin={plugin}
|
plugin={plugin}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
import { render, screen, act } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { TestProvider } from 'test/helpers/TestProvider';
|
||||||
|
|
||||||
|
import { PluginSignatureStatus } from '@grafana/data';
|
||||||
|
|
||||||
|
import { PluginDetailsPage } from './PluginDetailsPage';
|
||||||
|
|
||||||
|
jest.mock('../state/hooks', () => ({
|
||||||
|
__esModule: true,
|
||||||
|
...jest.requireActual('../state/hooks'),
|
||||||
|
useGetSingle: jest.fn().mockImplementation((id: string) => {
|
||||||
|
return {
|
||||||
|
description: 'The test plugin',
|
||||||
|
downloads: 5,
|
||||||
|
id: 'test-plugin',
|
||||||
|
info: {
|
||||||
|
logos: { small: '', large: '' },
|
||||||
|
},
|
||||||
|
name: 'Testing Plugin',
|
||||||
|
orgName: 'Test',
|
||||||
|
popularity: 0,
|
||||||
|
signature: PluginSignatureStatus.valid,
|
||||||
|
publishedAt: '2020-09-01',
|
||||||
|
updatedAt: '2021-06-28',
|
||||||
|
hasUpdate: false,
|
||||||
|
isInstalled: false,
|
||||||
|
isCore: false,
|
||||||
|
isDev: false,
|
||||||
|
isEnterprise: false,
|
||||||
|
isDisabled: false,
|
||||||
|
isPublished: true,
|
||||||
|
angularDetected: id === 'angular',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('PluginDetailsAngularDeprecation', () => {
|
||||||
|
afterAll(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the component for angular plugins', async () => {
|
||||||
|
await act(async () =>
|
||||||
|
render(
|
||||||
|
<TestProvider>
|
||||||
|
<PluginDetailsPage pluginId="angular" />
|
||||||
|
</TestProvider>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(screen.getByText(/angular plugin/i)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render the component for non-angular plugins', async () => {
|
||||||
|
await act(async () =>
|
||||||
|
render(
|
||||||
|
<TestProvider>
|
||||||
|
<PluginDetailsPage pluginId="not-angular" />
|
||||||
|
</TestProvider>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(screen.queryByText(/angular plugin/i)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -3,12 +3,14 @@ import React from 'react';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { useStyles2, TabContent, Alert } from '@grafana/ui';
|
import { useStyles2, TabContent, Alert } from '@grafana/ui';
|
||||||
import { Layout } from '@grafana/ui/src/components/Layout/Layout';
|
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 { 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';
|
||||||
@ -73,6 +75,12 @@ export function PluginDetailsPage({
|
|||||||
<Page navId={navId} pageNav={navModel} actions={actions} subTitle={subtitle} info={info}>
|
<Page navId={navId} pageNav={navModel} actions={actions} subTitle={subtitle} info={info}>
|
||||||
<Page.Contents>
|
<Page.Contents>
|
||||||
<TabContent className={styles.tabContent}>
|
<TabContent className={styles.tabContent}>
|
||||||
|
{plugin.angularDetected && (
|
||||||
|
<PluginDetailsAngularDeprecation
|
||||||
|
className={styles.alert}
|
||||||
|
angularSupportEnabled={config?.angularSupportEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
<PluginDetailsSignature plugin={plugin} className={styles.alert} />
|
||||||
<PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
|
<PluginDetailsDisabledError plugin={plugin} className={styles.alert} />
|
||||||
<PluginDetailsBody queryParams={Object.fromEntries(queryParams)} plugin={plugin} pageId={activePageId} />
|
<PluginDetailsBody queryParams={Object.fromEntries(queryParams)} plugin={plugin} pageId={activePageId} />
|
||||||
|
@ -75,4 +75,14 @@ describe('PluginListItemBadges', () => {
|
|||||||
render(<PluginListItemBadges plugin={{ ...plugin, hasUpdate: true, installedVersion: '0.0.9' }} />);
|
render(<PluginListItemBadges plugin={{ ...plugin, hasUpdate: true, installedVersion: '0.0.9' }} />);
|
||||||
expect(screen.getByText(/update available/i)).toBeVisible();
|
expect(screen.getByText(/update available/i)).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders an angular badge (when plugin is angular)', () => {
|
||||||
|
render(<PluginListItemBadges plugin={{ ...plugin, angularDetected: true }} />);
|
||||||
|
expect(screen.getByText(/angular/i)).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render an angular badge (when plugin is not angular)', () => {
|
||||||
|
render(<PluginListItemBadges plugin={{ ...plugin, angularDetected: false }} />);
|
||||||
|
expect(screen.queryByText(/angular/i)).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,22 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { PluginType } from '@grafana/data';
|
||||||
import { HorizontalGroup, PluginSignatureBadge } from '@grafana/ui';
|
import { HorizontalGroup, PluginSignatureBadge } from '@grafana/ui';
|
||||||
|
|
||||||
import { CatalogPlugin } from '../types';
|
import { CatalogPlugin } from '../types';
|
||||||
|
|
||||||
import { PluginEnterpriseBadge, PluginDisabledBadge, PluginInstalledBadge, PluginUpdateAvailableBadge } from './Badges';
|
import {
|
||||||
|
PluginEnterpriseBadge,
|
||||||
|
PluginDisabledBadge,
|
||||||
|
PluginInstalledBadge,
|
||||||
|
PluginUpdateAvailableBadge,
|
||||||
|
PluginAngularBadge,
|
||||||
|
} from './Badges';
|
||||||
|
|
||||||
type PluginBadgeType = {
|
type PluginBadgeType = {
|
||||||
plugin: CatalogPlugin;
|
plugin: CatalogPlugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
||||||
|
// Currently renderer plugins are not supported by the catalog due to complications related to installation / update / uninstall.
|
||||||
|
const hasUpdate = plugin.hasUpdate && !plugin.isCore && plugin.type !== PluginType.renderer;
|
||||||
if (plugin.isEnterprise) {
|
if (plugin.isEnterprise) {
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup height="auto" wrap>
|
<HorizontalGroup height="auto" wrap>
|
||||||
<PluginEnterpriseBadge plugin={plugin} />
|
<PluginEnterpriseBadge plugin={plugin} />
|
||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
<PluginUpdateAvailableBadge plugin={plugin} />
|
{hasUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||||
|
{plugin.angularDetected && <PluginAngularBadge />}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -26,7 +36,8 @@ export function PluginListItemBadges({ plugin }: PluginBadgeType) {
|
|||||||
<PluginSignatureBadge status={plugin.signature} />
|
<PluginSignatureBadge status={plugin.signature} />
|
||||||
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
{plugin.isDisabled && <PluginDisabledBadge error={plugin.error} />}
|
||||||
{plugin.isInstalled && <PluginInstalledBadge />}
|
{plugin.isInstalled && <PluginInstalledBadge />}
|
||||||
<PluginUpdateAvailableBadge plugin={plugin} />
|
{hasUpdate && <PluginUpdateAvailableBadge plugin={plugin} />}
|
||||||
|
{plugin.angularDetected && <PluginAngularBadge />}
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
|
|||||||
updatedAt,
|
updatedAt,
|
||||||
createdAt: publishedAt,
|
createdAt: publishedAt,
|
||||||
status,
|
status,
|
||||||
|
angularDetected,
|
||||||
} = plugin;
|
} = plugin;
|
||||||
|
|
||||||
const isDisabled = !!error || isDisabledSecretsPlugin(typeCode);
|
const isDisabled = !!error || isDisabledSecretsPlugin(typeCode);
|
||||||
@ -90,6 +91,7 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C
|
|||||||
isEnterprise: status === 'enterprise',
|
isEnterprise: status === 'enterprise',
|
||||||
type: typeCode,
|
type: typeCode,
|
||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
|
angularDetected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +107,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
|||||||
signatureType,
|
signatureType,
|
||||||
hasUpdate,
|
hasUpdate,
|
||||||
accessControl,
|
accessControl,
|
||||||
|
angularDetected,
|
||||||
} = plugin;
|
} = plugin;
|
||||||
|
|
||||||
const isDisabled = !!error || isDisabledSecretsPlugin(type);
|
const isDisabled = !!error || isDisabledSecretsPlugin(type);
|
||||||
@ -132,6 +135,7 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat
|
|||||||
type,
|
type,
|
||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
accessControl: accessControl,
|
accessControl: accessControl,
|
||||||
|
angularDetected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +190,7 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e
|
|||||||
error: error?.errorCode,
|
error: error?.errorCode,
|
||||||
// Only local plugins have access control metadata
|
// Only local plugins have access control metadata
|
||||||
accessControl: local?.accessControl,
|
accessControl: local?.accessControl,
|
||||||
|
angularDetected: local?.angularDetected || remote?.angularDetected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
|
|||||||
installedVersion?: string;
|
installedVersion?: string;
|
||||||
details?: CatalogPluginDetails;
|
details?: CatalogPluginDetails;
|
||||||
error?: PluginErrorCode;
|
error?: PluginErrorCode;
|
||||||
|
angularDetected?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CatalogPluginDetails {
|
export interface CatalogPluginDetails {
|
||||||
@ -123,6 +124,7 @@ export type RemotePlugin = {
|
|||||||
versionSignedByOrg: string;
|
versionSignedByOrg: string;
|
||||||
versionSignedByOrgName: string;
|
versionSignedByOrgName: string;
|
||||||
versionStatus: string;
|
versionStatus: string;
|
||||||
|
angularDetected?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LocalPlugin = WithAccessControlMetadata & {
|
export type LocalPlugin = WithAccessControlMetadata & {
|
||||||
@ -156,6 +158,7 @@ export type LocalPlugin = WithAccessControlMetadata & {
|
|||||||
state: string;
|
state: string;
|
||||||
type: PluginType;
|
type: PluginType;
|
||||||
dependencies: PluginDependencies;
|
dependencies: PluginDependencies;
|
||||||
|
angularDetected: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Rel {
|
interface Rel {
|
||||||
|
Reference in New Issue
Block a user