chore: alerts fixes and improvements (#8327)

This commit is contained in:
Amlan Kumar Nandy 2025-06-23 14:08:17 +07:00 committed by GitHub
parent 7f5b388722
commit f0994e52c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 106 additions and 21 deletions

View File

@ -62,5 +62,8 @@
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
"webhook_url_required": "Webhook URL is mandatory", "webhook_url_required": "Webhook URL is mandatory",
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
} }

View File

@ -77,5 +77,8 @@
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
"webhook_url_required": "Webhook URL is mandatory", "webhook_url_required": "Webhook URL is mandatory",
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
} }

View File

@ -120,14 +120,19 @@ describe('Create Alert Channel', () => {
expect(screen.getByText('button_test_channel')).toBeInTheDocument(); expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument(); expect(screen.getByText('button_return')).toBeInTheDocument();
}); });
it('Should check if saving the form without filling the name displays "Something went wrong"', async () => { it('Should check if saving the form without filling the name displays error notification', async () => {
const saveButton = screen.getByRole('button', { const saveButton = screen.getByRole('button', {
name: 'button_save_channel', name: 'button_save_channel',
}); });
fireEvent.click(saveButton); fireEvent.click(saveButton);
await waitFor(() => expect(showErrorModal).toHaveBeenCalled()); await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
message: 'Error',
description: 'channel_name_required',
}),
);
}); });
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => { it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
server.use( server.use(

View File

@ -138,6 +138,14 @@ function CreateAlertChannels({
); );
const onSlackHandler = useCallback(async () => { const onSlackHandler = useCallback(async () => {
if (!selectedConfig.api_url) {
notifications.error({
message: 'Error',
description: t('webhook_url_required'),
});
return;
}
setSavingState(true); setSavingState(true);
try { try {
@ -154,7 +162,7 @@ function CreateAlertChannels({
} finally { } finally {
setSavingState(false); setSavingState(false);
} }
}, [prepareSlackRequest, notifications, t, showErrorModal]); }, [selectedConfig, notifications, t, prepareSlackRequest, showErrorModal]);
const prepareWebhookRequest = useCallback(() => { const prepareWebhookRequest = useCallback(() => {
// initial api request without auth params // initial api request without auth params
@ -192,6 +200,14 @@ function CreateAlertChannels({
}, [notifications, t, selectedConfig]); }, [notifications, t, selectedConfig]);
const onWebhookHandler = useCallback(async () => { const onWebhookHandler = useCallback(async () => {
if (!selectedConfig.api_url) {
notifications.error({
message: 'Error',
description: t('webhook_url_required'),
});
return;
}
setSavingState(true); setSavingState(true);
try { try {
const request = prepareWebhookRequest(); const request = prepareWebhookRequest();
@ -208,7 +224,13 @@ function CreateAlertChannels({
} finally { } finally {
setSavingState(false); setSavingState(false);
} }
}, [prepareWebhookRequest, notifications, t, showErrorModal]); }, [
selectedConfig.api_url,
notifications,
t,
prepareWebhookRequest,
showErrorModal,
]);
const preparePagerRequest = useCallback(() => { const preparePagerRequest = useCallback(() => {
const validationError = ValidatePagerChannel(selectedConfig as PagerChannel); const validationError = ValidatePagerChannel(selectedConfig as PagerChannel);
@ -272,6 +294,14 @@ function CreateAlertChannels({
); );
const onOpsgenieHandler = useCallback(async () => { const onOpsgenieHandler = useCallback(async () => {
if (!selectedConfig.api_key) {
notifications.error({
message: 'Error',
description: t('api_key_required'),
});
return;
}
setSavingState(true); setSavingState(true);
try { try {
await createOpsgenie(prepareOpsgenieRequest()); await createOpsgenie(prepareOpsgenieRequest());
@ -287,7 +317,13 @@ function CreateAlertChannels({
} finally { } finally {
setSavingState(false); setSavingState(false);
} }
}, [prepareOpsgenieRequest, notifications, t, showErrorModal]); }, [
selectedConfig.api_key,
notifications,
t,
prepareOpsgenieRequest,
showErrorModal,
]);
const prepareEmailRequest = useCallback( const prepareEmailRequest = useCallback(
() => ({ () => ({
@ -301,6 +337,14 @@ function CreateAlertChannels({
); );
const onEmailHandler = useCallback(async () => { const onEmailHandler = useCallback(async () => {
if (!selectedConfig.to) {
notifications.error({
message: 'Error',
description: t('to_required'),
});
return;
}
setSavingState(true); setSavingState(true);
try { try {
const request = prepareEmailRequest(); const request = prepareEmailRequest();
@ -317,7 +361,7 @@ function CreateAlertChannels({
} finally { } finally {
setSavingState(false); setSavingState(false);
} }
}, [prepareEmailRequest, notifications, t, showErrorModal]); }, [prepareEmailRequest, notifications, t, showErrorModal, selectedConfig.to]);
const prepareMsTeamsRequest = useCallback( const prepareMsTeamsRequest = useCallback(
() => ({ () => ({
@ -331,6 +375,14 @@ function CreateAlertChannels({
); );
const onMsTeamsHandler = useCallback(async () => { const onMsTeamsHandler = useCallback(async () => {
if (!selectedConfig.webhook_url) {
notifications.error({
message: 'Error',
description: t('webhook_url_required'),
});
return;
}
setSavingState(true); setSavingState(true);
try { try {
@ -347,10 +399,24 @@ function CreateAlertChannels({
} finally { } finally {
setSavingState(false); setSavingState(false);
} }
}, [prepareMsTeamsRequest, notifications, t, showErrorModal]); }, [
selectedConfig.webhook_url,
notifications,
t,
prepareMsTeamsRequest,
showErrorModal,
]);
const onSaveHandler = useCallback( const onSaveHandler = useCallback(
async (value: ChannelType) => { async (value: ChannelType) => {
if (!selectedConfig.name) {
notifications.error({
message: 'Error',
description: t('channel_name_required'),
});
return;
}
const functionMapper = { const functionMapper = {
[ChannelType.Slack]: onSlackHandler, [ChannelType.Slack]: onSlackHandler,
[ChannelType.Webhook]: onWebhookHandler, [ChannelType.Webhook]: onWebhookHandler,

View File

@ -28,26 +28,26 @@ function ExapandableRow({ allAlerts }: ExapandableRowProps): JSX.Element {
hoverable hoverable
key={alert.fingerprint} key={alert.fingerprint}
> >
<TableCell> <TableCell minWidth="90px">
<Status severity={alert.status.state} /> <Status severity={alert.status.state} />
</TableCell> </TableCell>
<TableCell> <TableCell minWidth="90px" overflowX="scroll">
<Typography>{labels.alertname}</Typography> <Typography>{labels.alertname}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell minWidth="90px">
<Typography>{labels.severity}</Typography> <Typography>{labels.severity}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell minWidth="90px">
<Typography>{`${formatTimezoneAdjustedTimestamp( <Typography>{`${formatTimezoneAdjustedTimestamp(
formatedDate, formatedDate,
DATE_TIME_FORMATS.UTC_US, DATE_TIME_FORMATS.UTC_US,
)}`}</Typography> )}`}</Typography>
</TableCell> </TableCell>
<TableCell> <TableCell minWidth="90px" overflowX="scroll">
<div> <div>
{tags.map((e) => ( {tags.map((e) => (
<Tag key={e}>{`${e}:${labels[e]}`}</Tag> <Tag key={e}>{`${e}:${labels[e]}`}</Tag>

View File

@ -19,7 +19,7 @@ function TableRowComponent({
return ( return (
<div> <div>
<TableRow> <TableRow>
<TableCell> <TableCell minWidth="90px">
<StatusContainer> <StatusContainer>
<IconContainer onClick={onClickHandler}> <IconContainer onClick={onClickHandler}>
{!isClicked ? <PlusSquareOutlined /> : <MinusSquareOutlined />} {!isClicked ? <PlusSquareOutlined /> : <MinusSquareOutlined />}
@ -33,10 +33,10 @@ function TableRowComponent({
</> </>
</StatusContainer> </StatusContainer>
</TableCell> </TableCell>
<TableCell /> <TableCell minWidth="90px" />
<TableCell /> <TableCell minWidth="90px" />
<TableCell /> <TableCell minWidth="90px" />
<TableCell /> <TableCell minWidth="90px" />
{/* <TableCell minWidth="200px"> {/* <TableCell minWidth="200px">
<Button type="primary">Resume Group</Button> <Button type="primary">Resume Group</Button>
</TableCell> */} </TableCell> */}

View File

@ -36,7 +36,9 @@ function FilteredTable({
<Container> <Container>
<TableHeaderContainer> <TableHeaderContainer>
{headers.map((header) => ( {headers.map((header) => (
<TableHeader key={header}>{header}</TableHeader> <TableHeader key={header} minWidth="90px">
{header}
</TableHeader>
))} ))}
</TableHeaderContainer> </TableHeaderContainer>

View File

@ -1,13 +1,14 @@
import { Card } from 'antd'; import { Card } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
export const TableHeader = styled(Card)` export const TableHeader = styled(Card)<Props>`
&&& { &&& {
flex: 1; flex: 1;
text-align: center; text-align: center;
.ant-card-body { .ant-card-body {
padding: 1rem; padding: 1rem;
} }
min-width: ${(props): string => props.minWidth || ''};
} }
`; `;
@ -37,6 +38,7 @@ export const TableRow = styled(Card)`
interface Props { interface Props {
minWidth?: string; minWidth?: string;
overflowX?: string;
} }
export const TableCell = styled.div<Props>` export const TableCell = styled.div<Props>`
&&& { &&& {
@ -45,6 +47,10 @@ export const TableCell = styled.div<Props>`
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
overflow-x: ${(props): string => props.overflowX || 'none'};
::-webkit-scrollbar {
height: ${(props): string => (props.overflowX ? '2px' : '8px')};
}
} }
`; `;