Revert "Plugins: Add PluginInsights UI (#111603)" (#115574)

This reverts commit 1f4f2b4d7c.
This commit is contained in:
Galen Kistler 2025-12-18 11:11:33 -06:00 committed by GitHub
parent 1862e5dac5
commit 051cdaad0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 16 additions and 566 deletions

View file

@ -2868,6 +2868,11 @@
"count": 1
}
},
"public/app/features/plugins/admin/components/PluginDetailsPage.tsx": {
"@typescript-eslint/consistent-type-assertions": {
"count": 1
}
},
"public/app/features/plugins/admin/helpers.ts": {
"no-restricted-syntax": {
"count": 2

View file

@ -1189,11 +1189,6 @@ export interface FeatureToggles {
*/
onlyStoreActionSets?: boolean;
/**
* Show insights for plugins in the plugin details page
* @default false
*/
pluginInsights?: boolean;
/**
* Enables a new panel time settings drawer
*/
panelTimeSettings?: boolean;

View file

@ -1960,14 +1960,6 @@ var (
Owner: identityAccessTeam,
Expression: "true",
},
{
Name: "pluginInsights",
Description: "Show insights for plugins in the plugin details page",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaPluginsPlatformSquad,
Expression: "false",
},
{
Name: "panelTimeSettings",
Description: "Enables a new panel time settings drawer",

View file

@ -266,7 +266,6 @@ jaegerEnableGrpcEndpoint,experimental,@grafana/oss-big-tent,false,false,false
pluginStoreServiceLoading,experimental,@grafana/plugins-platform-backend,false,false,false
newPanelPadding,preview,@grafana/dashboards-squad,false,false,true
onlyStoreActionSets,GA,@grafana/identity-access-team,false,false,false
pluginInsights,experimental,@grafana/plugins-platform-backend,false,false,true
panelTimeSettings,experimental,@grafana/dashboards-squad,false,false,false
elasticsearchRawDSLQuery,experimental,@grafana/partner-datasources,false,false,false
kubernetesAnnotations,experimental,@grafana/grafana-backend-services-squad,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
266 pluginStoreServiceLoading experimental @grafana/plugins-platform-backend false false false
267 newPanelPadding preview @grafana/dashboards-squad false false true
268 onlyStoreActionSets GA @grafana/identity-access-team false false false
pluginInsights experimental @grafana/plugins-platform-backend false false true
269 panelTimeSettings experimental @grafana/dashboards-squad false false false
270 elasticsearchRawDSLQuery experimental @grafana/partner-datasources false false false
271 kubernetesAnnotations experimental @grafana/grafana-backend-services-squad false false false

View file

@ -2693,20 +2693,6 @@
"expression": "false"
}
},
{
"metadata": {
"name": "pluginInsights",
"resourceVersion": "1761300628147",
"creationTimestamp": "2025-10-24T10:10:28Z"
},
"spec": {
"description": "Show insights for plugins in the plugin details page",
"stage": "experimental",
"codeowner": "@grafana/plugins-platform-backend",
"frontend": true,
"expression": "false"
}
},
{
"metadata": {
"name": "pluginInstallAPISync",

View file

@ -8,7 +8,6 @@ import {
LocalPlugin,
RemotePlugin,
CatalogPluginDetails,
CatalogPluginInsights,
Version,
PluginVersion,
InstancePlugin,
@ -48,21 +47,6 @@ export async function getPluginDetails(id: string): Promise<CatalogPluginDetails
};
}
export async function getPluginInsights(id: string, version: string | undefined): Promise<CatalogPluginInsights> {
if (!version) {
throw new Error('Version is required');
}
try {
const insights = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins/${id}/versions/${version}/insights`);
return insights;
} catch (error) {
if (isFetchError(error)) {
error.isHandled = true;
}
throw error;
}
}
export async function getRemotePlugins(): Promise<RemotePlugin[]> {
try {
const { items: remotePlugins }: { items: RemotePlugin[] } = await getBackendSrv().get(`${GCOM_API_ROOT}/plugins`, {

View file

@ -62,12 +62,10 @@ const plugin: CatalogPlugin = {
angularDetected: false,
isFullyInstalled: true,
accessControl: {},
insights: { id: 1, name: 'test-plugin', version: '1.0.0', insights: [] },
};
jest.mock('../state/hooks', () => ({
useGetSingle: jest.fn(),
useGetPluginInsights: jest.fn(),
useFetchStatus: jest.fn().mockReturnValue({ isLoading: false }),
useFetchDetailsStatus: () => ({ isLoading: false }),
useIsRemotePluginsAvailable: () => false,

View file

@ -16,19 +16,11 @@ import { PluginDetailsPanel } from '../components/PluginDetailsPanel';
import { PluginDetailsSignature } from '../components/PluginDetailsSignature';
import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs';
import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions';
import { useGetSingle, useFetchStatus, useFetchDetailsStatus, useGetPluginInsights } from '../state/hooks';
import { useGetSingle, useFetchStatus, useFetchDetailsStatus } from '../state/hooks';
import { PluginTabIds } from '../types';
import { PluginDetailsDeprecatedWarning } from './PluginDetailsDeprecatedWarning';
function isPluginTabId(value: string | null): value is PluginTabIds {
if (!value) {
return false;
}
const validIds: string[] = Object.values(PluginTabIds);
return validIds.includes(value);
}
export type Props = {
// The ID of the plugin
pluginId: string;
@ -57,13 +49,12 @@ export function PluginDetailsPage({
};
const queryParams = new URLSearchParams(location.search);
const plugin = useGetSingle(pluginId); // fetches the plugin settings for this Grafana instance
useGetPluginInsights(pluginId, plugin?.isInstalled ? plugin?.installedVersion : plugin?.latestVersion);
const isNarrowScreen = useMedia('(max-width: 600px)');
const pageParam = queryParams.get('page');
const pageId = pageParam && isPluginTabId(pageParam) ? pageParam : undefined;
const { navModel, activePageId } = usePluginDetailsTabs(plugin, pageId, isNarrowScreen);
const { navModel, activePageId } = usePluginDetailsTabs(
plugin,
queryParams.get('page') as PluginTabIds,
isNarrowScreen
);
const { actions, info, subtitle } = usePluginPageExtensions(plugin);
const { isLoading: isFetchLoading } = useFetchStatus();
const { isLoading: isFetchDetailsLoading } = useFetchDetailsStatus();

View file

@ -1,23 +1,11 @@
import userEvent from '@testing-library/user-event';
import { render, screen } from 'test/test-utils';
import { PluginSignatureStatus, PluginSignatureType, PluginType } from '@grafana/data';
import { config } from '@grafana/runtime';
import { CatalogPlugin, SCORE_LEVELS } from '../types';
import { CatalogPlugin } from '../types';
import { PluginDetailsPanel } from './PluginDetailsPanel';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
config: {
...jest.requireActual('@grafana/runtime').config,
featureToggles: {
pluginInsights: false,
},
},
}));
const mockPlugin: CatalogPlugin = {
description: 'Test plugin description',
downloads: 1000,
@ -197,61 +185,4 @@ describe('PluginDetailsPanel', () => {
expect(regularLinks).toContainElement(raiseIssueLink);
expect(regularLinks).not.toContainElement(websiteLink);
});
it('should render plugin insights when plugin has insights', async () => {
config.featureToggles.pluginInsights = true;
const pluginWithInsights = {
...mockPlugin,
insights: {
id: 1,
name: 'test-plugin',
version: '1.0.0',
insights: [
{
name: 'security',
scoreValue: 90,
scoreLevel: SCORE_LEVELS.EXCELLENT,
items: [
{
id: 'signature',
name: 'Signature verified',
level: 'ok' as const,
},
],
},
],
},
};
render(<PluginDetailsPanel plugin={pluginWithInsights} pluginExtentionsInfo={mockInfo} />);
expect(screen.getByTestId('plugin-insights-container')).toBeInTheDocument();
expect(screen.getByText('Plugin insights')).toBeInTheDocument();
expect(screen.queryByText('Security')).toBeInTheDocument();
await userEvent.click(screen.getByText('Security'));
expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument();
});
it('should not render plugin insights when plugin has no insights', () => {
const pluginWithoutInsights = {
...mockPlugin,
insights: undefined,
};
render(<PluginDetailsPanel plugin={pluginWithoutInsights} pluginExtentionsInfo={mockInfo} />);
expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument();
expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument();
});
it('should not render plugin insights when insights array is empty', () => {
const pluginWithEmptyInsights = {
...mockPlugin,
insights: {
id: 1,
name: 'test-plugin',
version: '1.0.0',
insights: [],
},
};
render(<PluginDetailsPanel plugin={pluginWithEmptyInsights} pluginExtentionsInfo={mockInfo} />);
expect(screen.queryByTestId('plugin-insights-container')).not.toBeInTheDocument();
expect(screen.queryByText('Plugin insights')).not.toBeInTheDocument();
});
});

View file

@ -3,7 +3,7 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { config, reportInteraction } from '@grafana/runtime';
import { reportInteraction } from '@grafana/runtime';
import { PageInfoItem } from '@grafana/runtime/internal';
import {
Stack,
@ -22,8 +22,6 @@ import { formatDate } from 'app/core/internationalization/dates';
import { CatalogPlugin } from '../types';
import { PluginInsights } from './PluginInsights';
type Props = { pluginExtentionsInfo: PageInfoItem[]; plugin: CatalogPlugin; width?: string };
export function PluginDetailsPanel(props: Props): React.ReactElement | null {
@ -71,11 +69,6 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null {
return (
<>
<Stack direction="column" gap={3} shrink={0} grow={0} width={width} data-testid="plugin-details-panel">
{config.featureToggles.pluginInsights && plugin.insights && plugin.insights?.insights?.length > 0 && (
<Box borderRadius="lg" padding={2} borderColor="medium" borderStyle="solid">
<PluginInsights pluginInsights={plugin.insights} />
</Box>
)}
<Box borderRadius="lg" padding={2} borderColor="medium" borderStyle="solid">
<Stack direction="column" gap={2}>
{pluginExtentionsInfo.map((infoItem, index) => {

View file

@ -1,171 +0,0 @@
import userEvent from '@testing-library/user-event';
import { render, screen } from 'test/test-utils';
import { CatalogPluginInsights, InsightLevel, SCORE_LEVELS } from '../types';
import { PluginInsights } from './PluginInsights';
const mockPluginInsights: CatalogPluginInsights = {
id: 1,
name: 'test-plugin',
version: '1.0.0',
insights: [
{
name: 'security',
scoreValue: 90,
scoreLevel: SCORE_LEVELS.EXCELLENT,
items: [
{
id: 'signature',
name: 'Signature verified',
description: 'Plugin signature is valid',
level: 'ok' as InsightLevel,
},
{
id: 'trackingscripts',
name: 'No unsafe JavaScript detected',
level: 'good' as InsightLevel,
},
],
},
{
name: 'quality',
scoreValue: 60,
scoreLevel: SCORE_LEVELS.FAIR,
items: [
{
id: 'metadatavalid',
name: 'Metadata is valid',
level: 'ok' as InsightLevel,
},
{
id: 'code-rules',
name: 'Missing code rules',
description: 'Plugin lacks comprehensive code rules',
level: 'warning' as InsightLevel,
},
],
},
],
};
const mockPluginInsightsWithPoorLevel: CatalogPluginInsights = {
id: 3,
name: 'test-plugin-poor',
version: '0.8.0',
insights: [
{
name: 'quality',
scoreValue: 35,
scoreLevel: SCORE_LEVELS.POOR,
items: [
{
id: 'legacy-platform',
name: 'Quality issues detected',
level: 'warning' as InsightLevel,
},
],
},
],
};
describe('PluginInsights', () => {
it('should render plugin insights section', () => {
render(<PluginInsights pluginInsights={mockPluginInsights} />);
const insightsSection = screen.getByTestId('plugin-insights-container');
expect(insightsSection).toBeInTheDocument();
expect(screen.getByText('Plugin insights')).toBeInTheDocument();
});
it('should render all insight categories with test ids', () => {
render(<PluginInsights pluginInsights={mockPluginInsights} />);
expect(screen.getByTestId('plugin-insight-security')).toBeInTheDocument();
expect(screen.getByTestId('plugin-insight-quality')).toBeInTheDocument();
});
it('should render category names with test ids', () => {
render(<PluginInsights pluginInsights={mockPluginInsights} />);
const securityCategory = screen.getByTestId('plugin-insight-security');
const qualityCategory = screen.getByTestId('plugin-insight-quality');
expect(securityCategory).toBeInTheDocument();
expect(securityCategory).toHaveTextContent('Security');
expect(qualityCategory).toBeInTheDocument();
expect(qualityCategory).toHaveTextContent('Quality');
});
it('should render individual insight items with test ids', async () => {
render(<PluginInsights pluginInsights={mockPluginInsights} />);
await userEvent.click(screen.getByText('Security'));
expect(screen.getByTestId('plugin-insight-item-signature')).toBeInTheDocument();
expect(screen.getByTestId('plugin-insight-item-trackingscripts')).toBeInTheDocument();
await userEvent.click(screen.getByText('Quality'));
expect(screen.getByTestId('plugin-insight-item-metadatavalid')).toBeInTheDocument();
expect(screen.getByTestId('plugin-insight-item-code-rules')).toBeInTheDocument();
});
it('should display correct icons for Excellent score level', () => {
render(<PluginInsights pluginInsights={mockPluginInsights} />);
const securityCategory = screen.getByTestId('plugin-insight-security');
const securityIcon = securityCategory.querySelector('[data-testid="excellent-icon"]');
expect(securityIcon).toBeInTheDocument();
});
it('should display correct icons for Poor score levels', () => {
// Test Poor level - should show exclamation-triangle
render(<PluginInsights pluginInsights={mockPluginInsightsWithPoorLevel} />);
const poorCategory = screen.getByTestId('plugin-insight-quality');
const poorIcon = poorCategory.querySelector('[data-testid="poor-icon"]');
expect(poorIcon).toBeInTheDocument();
});
it('should handle multiple items with different insight levels', async () => {
const multiLevelInsights: CatalogPluginInsights = {
id: 5,
name: 'multi-level-plugin',
version: '2.0.0',
insights: [
{
name: 'quality',
scoreValue: 75,
scoreLevel: SCORE_LEVELS.GOOD,
items: [
{
id: 'code-rules',
name: 'Info level item',
level: 'info' as InsightLevel,
},
{
id: 'sdk-usage',
name: 'OK level item',
level: 'ok' as InsightLevel,
},
{
id: 'jsMap',
name: 'Good level item',
level: 'good' as InsightLevel,
},
{
id: 'gosec',
name: 'Warning level item',
level: 'warning' as InsightLevel,
},
{
id: 'legacy-builder',
name: 'Danger level item',
level: 'danger' as InsightLevel,
},
],
},
],
};
render(<PluginInsights pluginInsights={multiLevelInsights} />);
await userEvent.click(screen.getByText('Quality'));
expect(screen.getByText('Info level item')).toBeInTheDocument();
expect(screen.getByText('OK level item')).toBeInTheDocument();
expect(screen.getByText('Good level item')).toBeInTheDocument();
expect(screen.getByText('Warning level item')).toBeInTheDocument();
expect(screen.getByText('Danger level item')).toBeInTheDocument();
});
});

View file

@ -1,140 +0,0 @@
import { css } from '@emotion/css';
import { capitalize } from 'lodash';
import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { Stack, Text, TextLink, CollapsableSection, Tooltip, Icon, useStyles2, useTheme2 } from '@grafana/ui';
import { CatalogPluginInsights } from '../types';
type Props = { pluginInsights: CatalogPluginInsights | undefined };
const PLUGINS_INSIGHTS_OPENED_EVENT_NAME = 'plugins_insights_opened';
export function PluginInsights(props: Props): React.ReactElement | null {
const { pluginInsights } = props;
const styles = useStyles2(getStyles);
const theme = useTheme2();
const [openInsights, setOpenInsights] = useState<Record<string, boolean>>({});
const handleInsightToggle = (insightName: string, isOpen: boolean) => {
if (isOpen) {
reportInteraction(PLUGINS_INSIGHTS_OPENED_EVENT_NAME, { insight: insightName });
}
setOpenInsights((prev) => ({ ...prev, [insightName]: isOpen }));
};
const tooltipInfo = (
<Stack direction="column" gap={0.5}>
<Stack direction="row" alignItems="center">
<Icon name="check-circle" size="md" color={theme.colors.success.main} />
<Text color="primary" variant="body">
<Trans i18nKey="plugins.details.labels.pluginInsightsSuccessTooltip">
All relevant signals are present and verified
</Trans>
</Text>
</Stack>
<Stack direction="row" alignItems="center">
<Icon name="exclamation-triangle" size="md" />
<Text color="primary" variant="body">
<Trans i18nKey="plugins.details.labels.pluginInsightsWarningTooltip">
One or more signals are missing or need attention
</Trans>
</Text>
</Stack>
<hr className={styles.pluginInsightsTooltipSeparator} />
<Text color="secondary" variant="body">
<Trans i18nKey="plugins.details.labels.moreDetails">
Do you find Plugin Insights usefull? Please share your feedback{' '}
<TextLink href="https://forms.gle/1ZVLbecyQ8aY9mDYA" external>
here
</TextLink>
.
</Trans>
</Text>
</Stack>
);
return (
<>
<Stack direction="column" gap={0.5} shrink={0} grow={0} data-testid="plugin-insights-container">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Text color="secondary" variant="h6" data-testid="plugin-insights-header">
<Trans i18nKey="plugins.details.labels.pluginInsights.header">Plugin insights</Trans>
</Text>
<Tooltip content={tooltipInfo} placement="right-end" interactive>
<Icon name="info-circle" size="md" />
</Tooltip>
</Stack>
{pluginInsights?.insights.map((insightItem, index) => {
return (
<Stack key={index} wrap direction="column" gap={1}>
<CollapsableSection
isOpen={openInsights[insightItem.name] ?? false}
onToggle={(isOpen) => handleInsightToggle(insightItem.name, isOpen)}
label={
<Stack
direction="row"
gap={1}
alignItems="center"
data-testid={`plugin-insight-${insightItem.name.toLowerCase()}`}
>
{insightItem.scoreLevel === 'Excellent' ? (
<Icon
name="check-circle"
size="lg"
color={theme.colors.success.main}
data-testid="excellent-icon"
/>
) : (
<Icon name="exclamation-triangle" size="lg" data-testid="poor-icon" />
)}
<Text
color="primary"
variant="body"
data-testid={`plugin-insight-color-${insightItem.name.toLowerCase()}`}
>
{capitalize(insightItem.name)}
</Text>
</Stack>
}
contentClassName={styles.pluginInsightsItems}
>
<Stack direction="column" gap={1}>
{insightItem.items.map((item, idx) => (
<Stack key={idx} direction="row" gap={1} alignItems="flex-start">
<span>
{item.level === 'good' ? (
<Icon name="check-circle" size="sm" color={theme.colors.success.main} />
) : (
<Icon name="exclamation-triangle" size="sm" />
)}
</span>
<Text color="secondary" variant="body" data-testid={`plugin-insight-item-${item.id}`}>
{item.name}
</Text>
</Stack>
))}
</Stack>
</CollapsableSection>
</Stack>
);
})}
</Stack>
</>
);
}
export const getStyles = (theme: GrafanaTheme2) => {
return {
pluginVersionDetails: css({ wordBreak: 'break-word' }),
pluginInsightsItems: css({ marginLeft: '26px', paddingTop: '0 !important' }),
pluginInsightsTooltipSeparator: css({
border: 'none',
borderTop: `1px solid ${theme.colors.border.medium}`,
margin: `${theme.spacing(1)} 0`,
}),
};
};

View file

@ -34,7 +34,6 @@ export default {
updatedAt: '2021-08-25T15:03:49.000Z',
version: '4.2.2',
error: undefined,
insights: { id: 1, name: 'alexanderzobnin-zabbix-app', version: '4.2.2', insights: [] },
details: {
grafanaDependency: '>=8.0.0',
pluginDependencies: [],
@ -382,7 +381,6 @@ export const datasourcePlugin = {
angularDetected: false,
isFullyInstalled: true,
latestVersion: '1.20.0',
insights: { id: 2, name: 'grafana-redshift-datasource', version: '1.20.0', insights: [] },
details: {
grafanaDependency: '>=8.0.0',
pluginDependencies: [],

View file

@ -31,9 +31,6 @@ export const getPluginsStateMock = (plugins: CatalogPlugin[] = []): ReducerState
'plugins/fetchDetails': {
status: RequestStatus.Fulfilled,
},
'plugins/fetchPluginInsights': {
status: RequestStatus.Fulfilled,
},
},
// Backward compatibility
plugins: [],
@ -78,11 +75,6 @@ export const mockPluginApis = ({
return Promise.resolve({ items: versions });
}
// Mock plugin insights - return empty insights to avoid API call errors
if (path.includes('/insights')) {
return Promise.resolve({ id: 1, name: '', version: '', insights: [] });
}
// Mock local plugin settings (installed) if necessary
if (local && path === `${API_ROOT}/${local.id}/settings`) {
return Promise.resolve(local);

View file

@ -13,7 +13,6 @@ import {
getPluginErrors,
getLocalPlugins,
getPluginDetails,
getPluginInsights,
installPlugin,
uninstallPlugin,
getInstancePlugins,
@ -166,22 +165,6 @@ export const fetchDetails = createAsyncThunk<Update<CatalogPlugin, string>, stri
}
);
export const fetchPluginInsights = createAsyncThunk<Update<CatalogPlugin, string>, { id: string; version?: string }>(
`${STATE_PREFIX}/fetchPluginInsights`,
async ({ id, version }, thunkApi) => {
try {
const insights = await getPluginInsights(id, version);
return {
id,
changes: { insights },
};
} catch (e) {
return thunkApi.rejectWithValue('Unknown error.');
}
}
);
export const addPlugins = createAction<CatalogPlugin[]>(`${STATE_PREFIX}/addPlugins`);
// 1. gets remote equivalents from the store (if there are any)
@ -282,8 +265,7 @@ export const panelPluginLoaded = createAction<PanelPlugin>(`${STATE_PREFIX}/pane
// TODO<remove once the "plugin_admin_enabled" feature flag is removed>
export const loadPanelPlugin = (id: string): ThunkResult<Promise<PanelPlugin>> => {
return async (dispatch, getStore) => {
const state = getStore();
let plugin = state.plugins.panels[id];
let plugin = getStore().plugins.panels[id];
if (!plugin) {
plugin = await importPanelPlugin(id);

View file

@ -6,16 +6,7 @@ import { useDispatch, useSelector } from 'app/types/store';
import { sortPlugins, Sorters, isPluginUpdatable } from '../helpers';
import { CatalogPlugin, PluginStatus } from '../types';
import {
fetchAll,
fetchDetails,
fetchRemotePlugins,
install,
uninstall,
fetchAllLocal,
unsetInstall,
fetchPluginInsights,
} from './actions';
import { fetchAll, fetchDetails, fetchRemotePlugins, install, uninstall, fetchAllLocal, unsetInstall } from './actions';
import {
selectPlugins,
selectById,
@ -53,18 +44,13 @@ export const useGetUpdatable = () => {
};
};
export const useGetSingle = (id: string, version?: string): CatalogPlugin | undefined => {
export const useGetSingle = (id: string): CatalogPlugin | undefined => {
useFetchAll();
useFetchDetails(id);
return useSelector((state) => selectById(state, id));
};
export const useGetPluginInsights = (id: string, version: string | undefined): CatalogPlugin | undefined => {
useFetchPluginInsights(id, version);
return useSelector((state) => selectById(state, id));
};
export const useGetSingleLocalWithoutDetails = (id: string): CatalogPlugin | undefined => {
useFetchAllLocal();
return useSelector((state) => selectById(state, id));
@ -167,17 +153,6 @@ export const useFetchDetails = (id: string) => {
}, [plugin]); // eslint-disable-line
};
export const useFetchPluginInsights = (id: string, version: string | undefined) => {
const dispatch = useDispatch();
const plugin = useSelector((state) => selectById(state, id));
const isNotFetching = !useSelector(selectIsRequestPending(fetchPluginInsights.typePrefix));
const shouldFetch = isNotFetching && plugin && !plugin.insights && version;
useEffect(() => {
shouldFetch && dispatch(fetchPluginInsights({ id, version }));
}, [plugin, version]); // eslint-disable-line
};
export const useFetchDetailsLazy = () => {
const dispatch = useDispatch();

View file

@ -7,7 +7,6 @@ import { CatalogPlugin, ReducerState, RequestStatus } from '../types';
import {
fetchDetails,
fetchPluginInsights,
install,
uninstall,
loadPluginDashboards,
@ -64,10 +63,6 @@ const slice = createSlice({
.addCase(fetchDetails.fulfilled, (state, action) => {
pluginsAdapter.updateOne(state.items, action.payload);
})
// Fetch Plugin Insights
.addCase(fetchPluginInsights.fulfilled, (state, action) => {
pluginsAdapter.updateOne(state.items, action.payload);
})
// Install
.addCase(install.fulfilled, (state, action) => {
pluginsAdapter.updateOne(state.items, action.payload);

View file

@ -55,7 +55,6 @@ export interface CatalogPlugin extends WithAccessControlMetadata {
updatedAt: string;
installedVersion?: string;
details?: CatalogPluginDetails;
insights?: CatalogPluginInsights;
error?: PluginErrorCode;
angularDetected?: boolean;
// instance plugins may not be fully installed, which means a new instance
@ -91,54 +90,6 @@ export interface CatalogPluginDetails {
screenshots?: Screenshots[] | null;
}
export type InsightLevel = 'ok' | 'warning' | 'danger' | 'good' | 'info';
export const SCORE_LEVELS = {
EXCELLENT: 'Excellent',
GOOD: 'Good',
FAIR: 'Fair',
POOR: 'Poor',
CRITICAL: 'Critical',
} as const;
export type ScoreLevel = (typeof SCORE_LEVELS)[keyof typeof SCORE_LEVELS];
export const INSIGHT_CATEGORIES = {
SECURITY: 'security',
QUALITY: 'quality',
PERFORMANCE: 'performance',
} as const;
export const INSIGHT_LEVELS = {
GOOD: 'good',
OK: 'ok',
WARNING: 'warning',
DANGER: 'danger',
INFO: 'info',
} as const;
export interface InsightItem {
id: string;
name: string;
description?: string;
level: InsightLevel;
link?: string;
}
export interface InsightCategory {
name: string;
items: InsightItem[];
scoreValue: number;
scoreLevel: ScoreLevel;
}
export interface CatalogPluginInsights {
id: number;
name: string;
version: string;
insights: InsightCategory[];
}
export interface CatalogPluginInfo {
logos: { large: string; small: string };
keywords: string[];

View file

@ -11390,12 +11390,6 @@
"latestReleaseDate": "Latest release date:",
"latestVersion": "Latest Version",
"license": "License",
"moreDetails": "Do you find Plugin Insights usefull? Please share your feedback <2>here</2>.",
"pluginInsights": {
"header": "Plugin insights"
},
"pluginInsightsSuccessTooltip": "All relevant signals are present and verified",
"pluginInsightsWarningTooltip": "One or more signals are missing or need attention",
"raiseAnIssue": "Raise an issue",
"reportAbuse": "Report a concern",
"reportAbuseTooltip": "Report issues related to malicious or harmful plugins directly to Grafana Labs.",