Alerting: Show legacy provisioned alert rules warning (#81902)

This commit is contained in:
Gilles De Mey 2024-02-16 14:47:03 +01:00 committed by GitHub
parent 6ce0efeb41
commit 248031d007
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 136 additions and 8 deletions

View File

@ -2,7 +2,8 @@ import React from 'react';
import { useAsync } from 'react-use';
import { LoadingPlaceholder } from '@grafana/ui';
import { useDispatch } from 'app/types';
import { getDashboardScenePageStateManager } from 'app/features/dashboard-scene/pages/DashboardScenePageStateManager';
import { DashboardRoutes, useDispatch } from 'app/types';
import { RulesTable } from '../components/rules/RulesTable';
import { useCombinedRuleNamespaces } from '../hooks/useCombinedRuleNamespaces';
@ -10,14 +11,26 @@ import { fetchPromAndRulerRulesAction } from '../state/actions';
import { Annotation } from '../utils/constants';
import { GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import LegacyAlertsDeprecationNotice from './LegacyAlertsDeprecationNotice';
interface Props {
dashboardUid: string;
}
export default function AlertRulesDrawerContent({ dashboardUid }: Props) {
const dispatch = useDispatch();
const dashboardStateManager = getDashboardScenePageStateManager();
const { loading } = useAsync(async () => {
const { value: dashboard, loading: loadingDashboardState } = useAsync(() => {
return dashboardStateManager
.fetchDashboard({
uid: dashboardUid,
route: DashboardRoutes.Normal,
})
.then((data) => (data ? data.dashboard : undefined));
}, [dashboardStateManager]);
const { loading: loadingAlertRules } = useAsync(async () => {
await dispatch(fetchPromAndRulerRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME }));
}, [dispatch]);
@ -27,12 +40,17 @@ export default function AlertRulesDrawerContent({ dashboardUid }: Props) {
.flatMap((g) => g.rules)
.filter((rule) => rule.annotations[Annotation.dashboardUID] === dashboardUid);
const loading = loadingDashboardState || loadingAlertRules;
return (
<>
{loading ? (
<LoadingPlaceholder text="Loading alert rules" />
) : (
<RulesTable rules={rules} showNextEvaluationColumn={false} showGroupColumn={false} />
<>
<LegacyAlertsDeprecationNotice dashboard={dashboard} />
<RulesTable rules={rules} showNextEvaluationColumn={false} showGroupColumn={false} />
</>
)}
</>
);

View File

@ -0,0 +1,92 @@
import { isEmpty } from 'lodash';
import React from 'react';
import { useToggle } from 'react-use';
import { config } from '@grafana/runtime';
import { Dashboard, Panel, RowPanel } from '@grafana/schema';
import { Alert, Collapse, Column, InteractiveTable, TextLink } from '@grafana/ui';
import { makePanelLink } from '../utils/misc';
interface DeprecationNoticeProps {
dashboard: Dashboard;
}
const usingLegacyAlerting = !config.unifiedAlertingEnabled;
export default function LegacyAlertsDeprecationNotice({ dashboard }: DeprecationNoticeProps) {
// if the user is still using legacy alerting we don't need to show any notice at all they will probably keep using legacy alerting and do not intend to upgrade.
if (usingLegacyAlerting) {
return null;
}
const panelsWithLegacyAlerts = getLegacyAlertPanelsFromDashboard(dashboard);
// don't show anything when the user has no legacy alerts defined
const hasLegacyAlerts = !isEmpty(panelsWithLegacyAlerts);
if (!hasLegacyAlerts) {
return null;
}
return dashboard.uid ? <LegacyAlertsWarning dashboardUid={dashboard.uid} panels={panelsWithLegacyAlerts} /> : null;
}
/**
* This function uses two different ways to detect legacy alerts based on what dashboard system is being used.
*
* 1. if using the older (non-scenes) dashboard system we can simply check for "alert" in the panel definition.
* 2. for dashboard scenes the alerts are no longer added to the model but we can check for "alertThreshold" in the panel options object
*/
function getLegacyAlertPanelsFromDashboard(dashboard: Dashboard): Panel[] {
const panelsWithLegacyAlerts = dashboard.panels?.filter((panel) => {
const hasAlertDefinition = 'alert' in panel;
const hasAlertThreshold = 'options' in panel && panel.options ? 'alertThreshold' in panel.options : false;
return hasAlertDefinition || hasAlertThreshold;
});
return panelsWithLegacyAlerts ?? [];
}
interface Props {
dashboardUid: string;
panels: Panel[];
}
function LegacyAlertsWarning({ dashboardUid, panels }: Props) {
const [isOpen, toggleCollapsible] = useToggle(false);
const columns: Array<Column<Panel | RowPanel>> = [
{ id: 'id', header: 'ID' },
{
id: 'title',
header: 'Title',
cell: (cell) => (
<TextLink
external
href={makePanelLink(dashboardUid, String(cell.row.id), { editPanel: cell.row.id, tab: 'alert' })}
>
{cell.value}
</TextLink>
),
},
];
return (
<Alert severity="warning" title="Legacy alert rules are deprecated">
<p>
You have legacy alert rules in this dashboard that were deprecated in Grafana 11 and are no longer supported.
</p>
<p>
Refer to{' '}
<TextLink href="https://grafana.com/docs/grafana/latest/alerting/set-up/migrating-alerts/" external>
our documentation
</TextLink>{' '}
on how to migrate legacy alert rules and how to import and export using Grafana Alerting.
</p>
<Collapse label={'List of panels using legacy alerts'} collapsible isOpen={isOpen} onToggle={toggleCollapsible}>
<InteractiveTable columns={columns} data={panels} getRowId={(panel) => String(panel.id)} pageSize={5} />
</Collapse>
</Alert>
);
}

View File

@ -157,8 +157,15 @@ export function makeDashboardLink(dashboardUID: string): string {
return createUrl(`/d/${encodeURIComponent(dashboardUID)}`);
}
export function makePanelLink(dashboardUID: string, panelId: string): string {
return createUrl(`/d/${encodeURIComponent(dashboardUID)}`, { viewPanel: panelId });
type PanelLinkParams = {
viewPanel?: string;
editPanel?: string;
tab?: 'alert' | 'transform' | 'query';
};
export function makePanelLink(dashboardUID: string, panelId: string, queryParams: PanelLinkParams = {}): string {
const panelParams = new URLSearchParams(queryParams);
return createUrl(`/d/${encodeURIComponent(dashboardUID)}`, panelParams);
}
// keep retrying fn if it's error passes shouldRetry(error) and timeout has not elapsed yet

View File

@ -3,9 +3,11 @@ import React from 'react';
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
import { SceneComponentProps, SceneObjectBase, sceneUtils } from '@grafana/scenes';
import { Dashboard } from '@grafana/schema';
import { Button, CodeEditor, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import LegacyAlertsDeprecationNotice from 'app/features/alerting/unified/integration/LegacyAlertsDeprecationNotice';
import { getPrettyJSON } from 'app/features/inspector/utils/utils';
import { DashboardDTO } from 'app/types';
@ -38,9 +40,13 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
return getDashboardSceneFor(this);
}
public getJsonText(): string {
public getSaveModel(): Dashboard {
const dashboard = this.getDashboard();
const jsonData = transformSceneToSaveModel(dashboard);
return transformSceneToSaveModel(dashboard);
}
public getJsonText(): string {
const jsonData = this.getSaveModel();
return getPrettyJSON(jsonData);
}
@ -67,6 +73,7 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
const { jsonText } = model.useState();
const styles = useStyles2(getStyles);
const saveModel = model.getSaveModel();
return (
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
@ -76,6 +83,7 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings,
panel settings, layout, queries, and so on.
</Trans>
<LegacyAlertsDeprecationNotice dashboard={saveModel} />
<CodeEditor
width="100%"
value={jsonText}

View File

@ -5,6 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Button, CodeEditor, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { Trans } from 'app/core/internationalization';
import LegacyAlertsDeprecationNotice from 'app/features/alerting/unified/integration/LegacyAlertsDeprecationNotice';
import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher';
import { getDashboardSrv } from '../../services/DashboardSrv';
@ -12,7 +13,8 @@ import { getDashboardSrv } from '../../services/DashboardSrv';
import { SettingsPageProps } from './types';
export function JsonEditorSettings({ dashboard, sectionNav }: SettingsPageProps) {
const [dashboardJson, setDashboardJson] = useState<string>(JSON.stringify(dashboard.getSaveModelClone(), null, 2));
const dashboardSaveModel = dashboard.getSaveModelClone();
const [dashboardJson, setDashboardJson] = useState<string>(JSON.stringify(dashboardSaveModel, null, 2));
const pageNav = sectionNav.node.parentItem;
const onClick = async () => {
@ -29,6 +31,7 @@ export function JsonEditorSettings({ dashboard, sectionNav }: SettingsPageProps)
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, panel
settings, layout, queries, and so on.
</Trans>
<LegacyAlertsDeprecationNotice dashboard={dashboardSaveModel} />
<CodeEditor
value={dashboardJson}
language="json"