mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-27 09:22:12 +00:00
Compare commits
1 Commits
perf/log-v
...
SIG-9743
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c84749db67 |
@@ -423,7 +423,7 @@ describe('Footer utils', () => {
|
||||
description:
|
||||
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})',
|
||||
summary:
|
||||
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})',
|
||||
'The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}',
|
||||
},
|
||||
condition: {
|
||||
alertOnAbsent: false,
|
||||
|
||||
@@ -278,7 +278,7 @@ export function buildCreateThresholdAlertRulePayload(
|
||||
labels: basicAlertState.labels,
|
||||
annotations: {
|
||||
description: notificationSettings.description,
|
||||
summary: notificationSettings.description,
|
||||
summary: notificationSettings.summary,
|
||||
},
|
||||
notificationSettings: notificationSettingsProps,
|
||||
version: 'v5',
|
||||
|
||||
@@ -11,6 +11,7 @@ import AdvancedOptionItem from '../EvaluationSettings/AdvancedOptionItem';
|
||||
import Stepper from '../Stepper';
|
||||
import MultipleNotifications from './MultipleNotifications';
|
||||
import NotificationMessage from './NotificationMessage';
|
||||
import NotificationSummary from './NotificationSummary';
|
||||
|
||||
function NotificationSettings(): JSX.Element {
|
||||
const {
|
||||
@@ -84,6 +85,7 @@ function NotificationSettings(): JSX.Element {
|
||||
<div className="notification-settings-container">
|
||||
<Stepper stepNumber={3} label="Notification settings" />
|
||||
<NotificationMessage />
|
||||
<NotificationSummary />
|
||||
<div className="notification-settings-content">
|
||||
<MultipleNotifications />
|
||||
<AdvancedOptionItem
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Tooltip, Typography } from 'antd';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
import { Info } from 'lucide-react';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
|
||||
function NotificationSummary(): JSX.Element {
|
||||
const {
|
||||
notificationSettings,
|
||||
setNotificationSettings,
|
||||
} = useCreateAlertState();
|
||||
|
||||
return (
|
||||
<div className="notification-summary-container">
|
||||
<div className="notification-summary-header">
|
||||
<div className="notification-summary-header-content">
|
||||
<Typography.Text className="notification-summary-header-title">
|
||||
Notification Summary
|
||||
<Tooltip title="Customize the summary content sent in alert notifications. Template variables like {{alertname}}, {{value}}, and {{threshold}} will be replaced with actual values when the alert fires.">
|
||||
<Info size={16} />
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
<Typography.Text className="notification-summary-header-description">
|
||||
Custom summary content for alert notifications. Use template variables to
|
||||
include dynamic information.
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
<TextArea
|
||||
value={notificationSettings.summary}
|
||||
onChange={(e): void =>
|
||||
setNotificationSettings({
|
||||
type: 'SET_SUMMARY',
|
||||
payload: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="Enter notification summary..."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotificationSummary;
|
||||
@@ -0,0 +1,75 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as createAlertContext from 'container/CreateAlertV2/context';
|
||||
import { createMockAlertContextState } from 'container/CreateAlertV2/EvaluationSettings/__tests__/testUtils';
|
||||
|
||||
import NotificationSummary from '../NotificationSummary';
|
||||
|
||||
jest.mock('uplot', () => {
|
||||
const paths = {
|
||||
spline: jest.fn(),
|
||||
bars: jest.fn(),
|
||||
};
|
||||
const uplotMock = jest.fn(() => ({
|
||||
paths,
|
||||
}));
|
||||
return {
|
||||
paths,
|
||||
default: uplotMock,
|
||||
};
|
||||
});
|
||||
|
||||
const mockSetNotificationSettings = jest.fn();
|
||||
const initialNotificationSettingsState = createMockAlertContextState()
|
||||
.notificationSettings;
|
||||
jest.spyOn(createAlertContext, 'useCreateAlertState').mockReturnValue(
|
||||
createMockAlertContextState({
|
||||
notificationSettings: {
|
||||
...initialNotificationSettingsState,
|
||||
summary: '',
|
||||
},
|
||||
setNotificationSettings: mockSetNotificationSettings,
|
||||
}),
|
||||
);
|
||||
|
||||
describe('NotificationSummary', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders textarea with message and placeholder', () => {
|
||||
render(<NotificationSummary />);
|
||||
expect(screen.getByText('Notification Summary')).toBeInTheDocument();
|
||||
const textarea = screen.getByPlaceholderText('Enter notification summary...');
|
||||
expect(textarea).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates notification summary when textarea value changes', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<NotificationSummary />);
|
||||
const textarea = screen.getByPlaceholderText('Enter notification summary...');
|
||||
await user.type(textarea, 'x');
|
||||
expect(mockSetNotificationSettings).toHaveBeenLastCalledWith({
|
||||
type: 'SET_SUMMARY',
|
||||
payload: 'x',
|
||||
});
|
||||
});
|
||||
|
||||
it('displays existing description value', () => {
|
||||
jest.spyOn(createAlertContext, 'useCreateAlertState').mockImplementation(
|
||||
() =>
|
||||
({
|
||||
notificationSettings: {
|
||||
summary: 'Existing summary',
|
||||
},
|
||||
setNotificationSettings: mockSetNotificationSettings,
|
||||
} as any),
|
||||
);
|
||||
|
||||
render(<NotificationSummary />);
|
||||
|
||||
const textarea = screen.getByDisplayValue('Existing summary');
|
||||
expect(textarea).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -63,6 +63,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
.notification-summary-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: -8px;
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
padding: 16px;
|
||||
|
||||
.notification-summary-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.notification-summary-header-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.notification-summary-header-title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: var(--bg-vanilla-300);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.notification-summary-header-description {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-summary-header-actions {
|
||||
.ant-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
color: var(--bg-robin-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
background: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-200);
|
||||
border-radius: 4px;
|
||||
color: var(--bg-vanilla-400) !important;
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-settings-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -290,6 +350,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
.notification-summary-container {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.notification-summary-header {
|
||||
.notification-summary-header-content {
|
||||
.notification-summary-header-title {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.notification-summary-header-description {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.notification-summary-header-actions {
|
||||
.ant-btn {
|
||||
color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-settings-content {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
@@ -198,6 +198,8 @@ describe('CreateAlertV2 utils', () => {
|
||||
},
|
||||
description:
|
||||
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})',
|
||||
summary:
|
||||
'The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}',
|
||||
routingPolicies: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,6 +626,21 @@ describe('CreateAlertV2 Context Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set summary', () => {
|
||||
const summary = 'Custom alert summary with {{$value}}';
|
||||
const result = notificationSettingsReducer(
|
||||
INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
{
|
||||
type: 'SET_SUMMARY',
|
||||
payload: summary,
|
||||
},
|
||||
);
|
||||
expect(result).toEqual({
|
||||
...INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
summary,
|
||||
});
|
||||
});
|
||||
|
||||
it(TEST_RESET_TO_INITIAL_STATE, () => {
|
||||
const modifiedState: NotificationSettingsState = {
|
||||
multipleNotifications: ['channel1'],
|
||||
@@ -636,6 +651,7 @@ describe('CreateAlertV2 Context Utils', () => {
|
||||
conditions: ['firing'],
|
||||
},
|
||||
description: 'Modified description',
|
||||
summary: 'Modified summary',
|
||||
routingPolicies: true,
|
||||
};
|
||||
const result = notificationSettingsReducer(modifiedState, {
|
||||
@@ -654,6 +670,7 @@ describe('CreateAlertV2 Context Utils', () => {
|
||||
conditions: ['nodata'],
|
||||
},
|
||||
description: 'New description',
|
||||
summary: 'New summary',
|
||||
routingPolicies: true,
|
||||
};
|
||||
const result = notificationSettingsReducer(
|
||||
|
||||
@@ -179,6 +179,8 @@ export const RE_NOTIFICATION_TIME_UNIT_OPTIONS = [
|
||||
|
||||
export const NOTIFICATION_MESSAGE_PLACEHOLDER =
|
||||
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})';
|
||||
export const NOTIFICATION_SUMMARY_PLACEHOLDER =
|
||||
'The rule threshold is set to {{$threshold}}, and the observed metric value is {{$value}}';
|
||||
|
||||
export const RE_NOTIFICATION_CONDITION_OPTIONS = [
|
||||
{ value: 'firing', label: 'Firing' },
|
||||
@@ -194,5 +196,6 @@ export const INITIAL_NOTIFICATION_SETTINGS_STATE: NotificationSettingsState = {
|
||||
conditions: [],
|
||||
},
|
||||
description: NOTIFICATION_MESSAGE_PLACEHOLDER,
|
||||
summary: NOTIFICATION_SUMMARY_PLACEHOLDER,
|
||||
routingPolicies: false,
|
||||
};
|
||||
|
||||
@@ -251,6 +251,7 @@ export interface NotificationSettingsState {
|
||||
conditions: ('firing' | 'nodata')[];
|
||||
};
|
||||
description: string;
|
||||
summary: string;
|
||||
routingPolicies: boolean;
|
||||
}
|
||||
|
||||
@@ -269,6 +270,7 @@ export type NotificationSettingsAction =
|
||||
};
|
||||
}
|
||||
| { type: 'SET_DESCRIPTION'; payload: string }
|
||||
| { type: 'SET_SUMMARY'; payload: string }
|
||||
| { type: 'SET_ROUTING_POLICIES'; payload: boolean }
|
||||
| { type: 'SET_INITIAL_STATE'; payload: NotificationSettingsState }
|
||||
| { type: 'RESET' };
|
||||
|
||||
@@ -241,6 +241,8 @@ export const notificationSettingsReducer = (
|
||||
return { ...state, reNotification: action.payload };
|
||||
case 'SET_DESCRIPTION':
|
||||
return { ...state, description: action.payload };
|
||||
case 'SET_SUMMARY':
|
||||
return { ...state, summary: action.payload };
|
||||
case 'SET_ROUTING_POLICIES':
|
||||
return { ...state, routingPolicies: action.payload };
|
||||
case 'RESET':
|
||||
|
||||
@@ -178,6 +178,7 @@ export function getNotificationSettingsStateFromAlertDef(
|
||||
alertDef: PostableAlertRuleV2,
|
||||
): NotificationSettingsState {
|
||||
const description = alertDef.annotations?.description || '';
|
||||
const summary = alertDef.annotations?.summary || '';
|
||||
const multipleNotifications = alertDef.notificationSettings?.groupBy || [];
|
||||
const routingPolicies = alertDef.notificationSettings?.usePolicy || false;
|
||||
|
||||
@@ -197,6 +198,7 @@ export function getNotificationSettingsStateFromAlertDef(
|
||||
return {
|
||||
...INITIAL_NOTIFICATION_SETTINGS_STATE,
|
||||
description,
|
||||
summary,
|
||||
multipleNotifications,
|
||||
routingPolicies,
|
||||
reNotification: {
|
||||
|
||||
Reference in New Issue
Block a user