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
5 changed files with 136 additions and 8 deletions
@@ -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} />
</>
)}
</>
);
@@ -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>
);
}
@@ -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
@@ -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}
@@ -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"