Skip to content

Commit 2680b34

Browse files
authored
PMM-10344: new Alerting for Grafana 9 (#482)
* PMM-10091 add changes from alerting poc * PMM-10091 fix tabs height * PMM-10091 fix highlight param * PMM-10091 fix table width break * PMM-10344 add percona to rule types * PMM-10344 add templated alert to selection * PMM-10344 first migration to react-hook-form * PMM-10344 first working version * PMM-10344 use grafana's Label * PMM-10344 remove unused code * PMM-10344 add error messages * PMM-10344 make templated alert rule default * PMM-10344 cleanup alerts code * PMM-10344 alert rule types on core * PMM-10344 fix imports * PMM-10344 fix getFloatDescription * PMM-10344 add key to templates dropdown * PMM-10344 check IA status * PMM-10344 fix tabs height * PMM-10344 link from templates to new rule * PMM-10344 use query params to select template * PMM-10344 add loading status to select * PMM-10344 first version with alertmanager's api * PMM-10344 use actions to show/hide details * PMM-10344 adapt test * PMM-10344 send Alerting toggle out of tech preview * PMM-10344 remove notifs channels page * PMM-10344 fix alerting tests * PMM-10344 remove useless backticks * PMM-10344 use receivers from store * PMM-10344 use redux to store rule templates * PMM-10344 remove moment from AlertRuleTemplate * PMM-10344 fix date format * PMM-10344 change copy * PMM-10344 remove disabled field * PMM-10344 remove notification channels * PMM-10344 change filter fields * PMM-10344 Allow group/folder selection * PMM-10344 add @percona annotations * PMM-10344 change API endpoint * PMM-10344 set minimum duration * PMM-10344 "for" as string * PMM-10344 fix double error toast * PMM-10344 change duration label * PMM-10344 send duration always as seconds * PMM-10344 hide communication settings * PMM-10344 remove alerting item if off * PMM-10344 use default name * PMM-10344 set default group * PMM-10344 break word in rules table * PMM-10344 fix double tabs * PMM-10344 fix bad import in test
1 parent 617b722 commit 2680b34

File tree

155 files changed

+1309
-5557
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+1309
-5557
lines changed

packages/grafana-data/src/types/datasource.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ abstract class DataSourceApi<
341341
* Defines new variable support
342342
* @alpha -- experimental
343343
*/
344+
// @ts-ignore
344345
variables?:
345346
| StandardVariableSupport<DataSourceApi<TQuery, TOptions>>
346347
| CustomVariableSupport<DataSourceApi<TQuery, TOptions>>

packages/grafana-ui/src/components/Tabs/TabsBar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ const getTabsBarStyles = stylesFactory((theme: GrafanaTheme2, hideBorder = false
2626
tabs: css`
2727
position: relative;
2828
display: flex;
29-
height: ${theme.components.menuTabs.height}px;
29+
${!!vertical ? 'height' : 'min-height'}: ${theme.components.menuTabs.height}px;
3030
flex-direction: ${!!vertical ? 'column' : 'row'};
31+
flex-wrap: ${!!vertical ? 'no-wrap' : 'wrap'};
3132
`,
3233
};
3334
});

public/app/core/components/NavBar/NavBar.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { NavBarToggle } from './NavBarToggle';
2727
import {
2828
getPmmSettingsPage,
2929
PMM_ADD_INSTANCE_PAGE,
30-
PMM_ALERTING_PAGE,
3130
PMM_BACKUP_PAGE,
3231
PMM_DBAAS_PAGE,
3332
PMM_ENTITLEMENTS_PAGE,
@@ -46,6 +45,7 @@ import {
4645
getActiveItem,
4746
isMatchOrInnerMatch,
4847
isSearchActive,
48+
removeAlertingMenuItem,
4949
SEARCH_ITEM_ID,
5050
} from './utils';
5151

@@ -62,7 +62,7 @@ export const NavBar = React.memo(() => {
6262
const dispatch = useDispatch();
6363
const kiosk = getKioskMode();
6464
const { result } = useSelector(getPerconaSettings);
65-
const { alertingEnabled } = result!;
65+
const { alertingEnabled, sttEnabled, dbaasEnabled, backupEnabled } = result!;
6666
const { isPlatformUser, isAuthorized } = useSelector(getPerconaUser);
6767
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
6868
const [menuOpen, setMenuOpen] = useState(false);
@@ -114,11 +114,11 @@ export const NavBar = React.memo(() => {
114114
.map((item) => enrichWithClickDispatch(item, dispatch, dispatchOffset));
115115

116116
const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
117+
const iaMenuItem = alertingEnabled ? buildIntegratedAlertingMenuItem(coreItems) : removeAlertingMenuItem(coreItems);
117118

118119
// @PERCONA
119120
// All these dispatches are our pages
120121
dispatch(updateNavIndex(getPmmSettingsPage(alertingEnabled)));
121-
dispatch(updateNavIndex(PMM_ALERTING_PAGE));
122122
dispatch(updateNavIndex(PMM_STT_PAGE));
123123
dispatch(updateNavIndex(PMM_DBAAS_PAGE));
124124
dispatch(updateNavIndex(PMM_BACKUP_PAGE));
@@ -128,12 +128,14 @@ export const NavBar = React.memo(() => {
128128
dispatch(updateNavIndex(PMM_ENTITLEMENTS_PAGE));
129129
dispatch(updateNavIndex(PMM_ENVIRONMENT_OVERVIEW_PAGE));
130130

131+
if (iaMenuItem) {
132+
dispatch(updateNavIndex(iaMenuItem));
133+
}
134+
131135
// @PERCONA
132136
useEffect(() => {
133137
const updatedNavTree = cloneDeep(initialState);
134138

135-
const { sttEnabled, alertingEnabled, dbaasEnabled, backupEnabled } = result!;
136-
137139
// @PERCONA
138140
if (isPlatformUser) {
139141
updatedNavTree.push(PMM_ENTITLEMENTS_PAGE);
@@ -145,10 +147,6 @@ export const NavBar = React.memo(() => {
145147
if (isAuthorized) {
146148
buildInventoryAndSettings(updatedNavTree);
147149

148-
if (alertingEnabled) {
149-
buildIntegratedAlertingMenuItem(updatedNavTree);
150-
}
151-
152150
if (sttEnabled) {
153151
updatedNavTree.push(PMM_STT_PAGE);
154152
}

public/app/core/components/NavBar/constants.ts

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -100,42 +100,20 @@ export const PMM_BACKUP_PAGE: NavModelItem = {
100100
],
101101
};
102102

103-
export const PMM_ALERTING_PAGE: NavModelItem = {
104-
id: 'integrated-alerting',
105-
icon: 'bell',
106-
text: 'Integrated Alerting',
107-
url: `${config.appSubUrl}/integrated-alerting`,
108-
subTitle: 'Percona Integrated Alerting',
109-
section: NavSection.Core,
110-
breadcrumbs: [
111-
{
112-
title: 'Integrated Alerting',
113-
url: `${config.appSubUrl}/integrated-alerting`,
114-
},
115-
],
116-
children: [
117-
{
118-
id: 'integrated-alerting-alerts',
119-
text: 'Alerts',
120-
url: `${config.appSubUrl}/integrated-alerting/alerts`,
121-
},
122-
{
123-
id: 'integrated-alerting-rules',
124-
text: 'Alert Rules',
125-
url: `${config.appSubUrl}/integrated-alerting/alert-rules`,
126-
},
127-
{
128-
id: 'integrated-alerting-templates',
129-
text: 'Alert Rule Templates',
130-
url: `${config.appSubUrl}/integrated-alerting/alert-rule-templates`,
131-
},
132-
{
133-
id: 'integrated-alerting-notification-channels',
134-
text: 'Notification Channels',
135-
url: `${config.appSubUrl}/integrated-alerting/notification-channels`,
136-
},
137-
],
138-
};
103+
export const PMM_ALERTING_PERCONA_ALERTS: NavModelItem[] = [
104+
{
105+
id: 'integrated-alerting-alerts',
106+
text: 'Fired alerts',
107+
icon: 'info-circle',
108+
url: `${config.appSubUrl}/alerting/alerts`,
109+
},
110+
{
111+
id: 'integrated-alerting-templates',
112+
text: 'Alert rule templates',
113+
icon: 'brackets-curly',
114+
url: `${config.appSubUrl}/alerting/alert-rule-templates`,
115+
},
116+
];
139117

140118
export const PMM_INVENTORY_PAGE: NavModelItem = {
141119
id: 'inventory',
@@ -210,13 +188,14 @@ export const getPmmSettingsPage = (alertingEnabled = false): NavModelItem => {
210188
},
211189
];
212190

213-
if (alertingEnabled) {
214-
children.push({
215-
id: 'settings-communication',
216-
text: 'Communication',
217-
url: `${config.appSubUrl}/settings/communication`,
218-
});
219-
}
191+
// TODO remove after integrating SMTP/slack with Grafana's alerting system
192+
// if (alertingEnabled) {
193+
// children.push({
194+
// id: 'settings-communication',
195+
// text: 'Communication',
196+
// url: `${config.appSubUrl}/settings/communication`,
197+
// });
198+
// }
220199
const page: NavModelItem = {
221200
id: 'settings',
222201
icon: 'percona-setting',

public/app/core/components/NavBar/utils.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import appEvents from '../../app_events';
1212
import { getFooterLinks } from '../Footer/Footer';
1313
import { HelpModal } from '../help/HelpModal';
1414

15-
import { PMM_ADD_INSTANCE_PAGE } from './constants';
15+
import { PMM_ADD_INSTANCE_PAGE, PMM_ALERTING_PERCONA_ALERTS } from './constants';
1616

1717
export const SEARCH_ITEM_ID = 'search';
1818
export const NAV_MENU_PORTAL_CONTAINER_ID = 'navbar-menu-portal-container';
@@ -199,30 +199,33 @@ export function getNavModelItemKey(item: NavModelItem) {
199199
return item.id ?? item.text;
200200
}
201201

202-
export const buildIntegratedAlertingMenuItem = (mainLinks: NavModelItem[]): NavModelItem[] => {
203-
const integratedAlertingLink = {
204-
id: 'integrated-alerting',
205-
text: 'Integrated Alerting',
206-
icon: 'bell',
207-
url: `${config.appSubUrl}/integrated-alerting`,
208-
};
202+
export const buildIntegratedAlertingMenuItem = (mainLinks: NavModelItem[]): NavModelItem | undefined => {
203+
const alertingItem = mainLinks.find(({ id }) => id === 'alerting');
209204

210-
const alertingIndex = mainLinks.findIndex(({ id }) => id === 'alerting');
205+
if (alertingItem?.url) {
206+
alertingItem.url = `${config.appSubUrl}/alerting/alerts`;
207+
}
211208

212-
if (alertingIndex === -1) {
213-
mainLinks.push({
214-
id: 'alerting',
215-
text: 'Alerting',
216-
icon: 'bell',
217-
url: `${config.appSubUrl}/integrated-alerting/alerts`,
218-
subTitle: 'Alert rules & notifications',
219-
children: [integratedAlertingLink],
220-
});
221-
} else {
222-
mainLinks[alertingIndex].children?.unshift(integratedAlertingLink, DIVIDER);
209+
alertingItem?.children?.unshift(...PMM_ALERTING_PERCONA_ALERTS);
210+
return alertingItem;
211+
};
212+
213+
export const removeAlertingMenuItem = (mainLinks: NavModelItem[]) => {
214+
const alertingItem = mainLinks.find(({ id }) => id === 'alerting');
215+
216+
PMM_ALERTING_PERCONA_ALERTS.forEach((alertingTab, idx) => {
217+
const item = alertingItem?.children?.find((c) => c.id === alertingTab.id);
218+
219+
if (item) {
220+
alertingItem?.children?.splice(idx, 1);
221+
}
222+
});
223+
224+
if (alertingItem?.url) {
225+
alertingItem.url = `${config.appSubUrl}/alerting/list`;
223226
}
224227

225-
return mainLinks;
228+
return alertingItem;
226229
};
227230

228231
export const buildInventoryAndSettings = (mainLinks: NavModelItem[]): NavModelItem[] => {

public/app/core/components/Select/FolderPicker.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import appEvents from '../../app_events';
1414
export type FolderPickerFilter = (hits: DashboardSearchHit[]) => DashboardSearchHit[];
1515

1616
export interface Props {
17-
onChange: ($folder: { title: string; id: number }) => void;
17+
// @PERCONA
18+
// Added uid here
19+
onChange: ($folder: { title: string; id: number; uid: number }) => void;
1820
enableCreateNew?: boolean;
1921
rootName?: string;
2022
enableReset?: boolean;
@@ -137,7 +139,9 @@ export class FolderPicker extends PureComponent<Props, State> {
137139
{
138140
folder: newFolder,
139141
},
140-
() => this.props.onChange({ id: newFolder.value!, title: newFolder.label! })
142+
// @PERCONA
143+
// Added uid here
144+
() => this.props.onChange({ id: newFolder.value!, title: newFolder.label!, uid: newFolder.uid })
141145
);
142146
};
143147

@@ -147,7 +151,9 @@ export class FolderPicker extends PureComponent<Props, State> {
147151

148152
if (newFolder.id > -1) {
149153
appEvents.emit(AppEvents.alertSuccess, ['Folder Created', 'OK']);
150-
folder = { value: newFolder.id, label: newFolder.title };
154+
// @PERCONA
155+
// Added uid here
156+
folder = { value: newFolder.id, label: newFolder.title, uid: newFolder.uid };
151157
this.setState(
152158
{
153159
folder: newFolder,
@@ -201,7 +207,9 @@ export class FolderPicker extends PureComponent<Props, State> {
201207
() => {
202208
// if this is not the same as our initial value notify parent
203209
if (folder && folder.value !== initialFolderId) {
204-
this.props.onChange({ id: folder.value!, title: folder.label! });
210+
// @PERCONA
211+
// Added uid here
212+
this.props.onChange({ id: folder.value!, title: folder.label!, uid: folder.uid });
205213
}
206214
}
207215
);
@@ -234,7 +242,9 @@ export class FolderPicker extends PureComponent<Props, State> {
234242

235243
function mapSearchHitsToOptions(hits: DashboardSearchHit[], filter?: FolderPickerFilter) {
236244
const filteredHits = filter ? filter(hits) : hits;
237-
return filteredHits.map((hit) => ({ label: hit.title, value: hit.id }));
245+
// @PERCONA
246+
// Added uid here
247+
return filteredHits.map((hit) => ({ label: hit.title, value: hit.id, uid: hit.uid }));
238248
}
239249

240250
interface Args {

public/app/core/reducers/navModel.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,21 @@ export const navIndexReducer = (state: NavIndex = initialState, action: AnyActio
6363
if (updateNavIndex.match(action)) {
6464
const newPages: NavIndex = {};
6565
const payload = action.payload;
66-
67-
if (payload.children && payload.children.length) {
68-
for (const node of payload.children) {
69-
if (node.id) {
70-
newPages[node.id] = {
71-
...node,
72-
parentItem: payload,
73-
};
66+
if (payload) {
67+
if (payload.children && payload.children.length) {
68+
for (const node of payload.children) {
69+
if (node.id) {
70+
newPages[node.id] = {
71+
...node,
72+
parentItem: payload,
73+
};
74+
}
7475
}
76+
} else if (payload.id) {
77+
newPages[payload.id] = {
78+
...payload,
79+
};
7580
}
76-
} else if (payload.id) {
77-
newPages[payload.id] = {
78-
...payload,
79-
};
8081
}
8182

8283
return { ...state, ...newPages };

public/app/features/alerting/routes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const commonRoutes: RouteDescriptor[] = [
1414
{
1515
path: '/alerting',
1616
// eslint-disable-next-line react/display-name
17-
component: () => <Redirect to="/alerting/list" />,
17+
component: () => <Redirect to="/alerting/alerts" />,
1818
},
1919
];
2020

public/app/features/alerting/unified/components/DynamicTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ const getStyles = <T extends unknown>(
236236
`,
237237
bodyCell: css`
238238
overflow: hidden;
239+
// @PERCONA
240+
word-break: break-all;
239241
240242
${theme.breakpoints.down('sm')} {
241243
grid-column-end: right;

public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { css } from '@emotion/css';
22
import React, { FC, useMemo, useState } from 'react';
33
import { FormProvider, useForm, UseFormWatch } from 'react-hook-form';
4-
import { useDispatch } from 'react-redux';
4+
import { useDispatch, useSelector } from 'react-redux';
55
import { Link } from 'react-router-dom';
66

77
import { GrafanaTheme2 } from '@grafana/data';
88
import { Button, ConfirmModal, CustomScrollbar, PageToolbar, Spinner, useStyles2 } from '@grafana/ui';
99
import { useAppNotification } from 'app/core/copy/appNotification';
1010
import { useCleanup } from 'app/core/hooks/useCleanup';
1111
import { useQueryParams } from 'app/core/hooks/useQueryParams';
12+
import { getPerconaSettings } from 'app/percona/shared/core/selectors';
1213
import { RuleWithLocation } from 'app/types/unified-alerting';
1314

1415
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
@@ -23,6 +24,7 @@ import { DetailsStep } from './DetailsStep';
2324
import { GrafanaEvaluationBehavior } from './GrafanaEvaluationBehavior';
2425
import { NotificationsStep } from './NotificationsStep';
2526
import { RuleInspector } from './RuleInspector';
27+
import { TemplateStep } from './TemplateStep/TemplateStep';
2628
import { QueryAndAlertConditionStep } from './query-and-alert-condition/QueryAndAlertConditionStep';
2729

2830
type Props = {
@@ -35,6 +37,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
3537
const notifyApp = useAppNotification();
3638
const [queryParams] = useQueryParams();
3739
const [showEditYaml, setShowEditYaml] = useState(false);
40+
const { result } = useSelector(getPerconaSettings);
3841

3942
const returnTo: string = (queryParams['returnTo'] as string | undefined) ?? '/alerting/list';
4043
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
@@ -47,9 +50,12 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
4750
...getDefaultFormValues(),
4851
queries: getDefaultQueries(),
4952
...(queryParams['defaults'] ? JSON.parse(queryParams['defaults'] as string) : {}),
50-
type: RuleFormType.grafana,
53+
// @PERCONA
54+
// Set templated as default
55+
type: result && !!result.alertingEnabled ? RuleFormType.templated : RuleFormType.grafana,
56+
group: result && !!result.alertingEnabled ? 'default-alert-group' : '',
5157
};
52-
}, [existing, queryParams]);
58+
}, [existing, queryParams, result]);
5359

5460
const formAPI = useForm<RuleFormValues>({
5561
mode: 'onSubmit',
@@ -63,6 +69,7 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
6369
const dataSourceName = watch('dataSourceName');
6470

6571
const showStep2 = Boolean(type && (type === RuleFormType.grafana || !!dataSourceName));
72+
const showTemplateStep = type === RuleFormType.templated;
6673

6774
const submitState = useUnifiedAlertingSelector((state) => state.ruleForm.saveRule) || initialAsyncRequestState;
6875
useCleanup((state) => state.unifiedAlerting.ruleForm.saveRule);
@@ -152,6 +159,8 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
152159
<CustomScrollbar autoHeightMin="100%" hideHorizontalTrack={true}>
153160
<div className={styles.contentInner}>
154161
<QueryAndAlertConditionStep editingExistingRule={!!existing} />
162+
{/* @PERCONA */}
163+
{showTemplateStep && <TemplateStep />}
155164
{showStep2 && (
156165
<>
157166
{type === RuleFormType.grafana ? <GrafanaEvaluationBehavior /> : <CloudEvaluationBehavior />}

0 commit comments

Comments
 (0)