-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Alerting UI] Display a banner to users when some alerts have failures, added alert statuses column and filters #79038
Changes from 12 commits
a859a98
e9d8acc
e71e5b5
43c811a
4dd495c
2006ca3
9ed9ba5
f6fe82f
33bc87b
f90de57
c8b8572
086534b
64cf479
5814739
ec9ecee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,8 @@ import { | |
EuiSpacer, | ||
EuiBetaBadge, | ||
EuiButtonEmpty, | ||
EuiButton, | ||
EuiTextColor, | ||
} from '@elastic/eui'; | ||
import { FormattedMessage } from '@kbn/i18n/react'; | ||
import { i18n } from '@kbn/i18n'; | ||
|
@@ -105,11 +107,20 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({ | |
const [isEnabled, setIsEnabled] = useState<boolean>(alert.enabled); | ||
const [isMuted, setIsMuted] = useState<boolean>(alert.muteAll); | ||
const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false); | ||
const [dissmissAlertErrors, setDissmissAlertErrors] = useState<boolean>(false); | ||
|
||
const setAlert = async () => { | ||
history.push(routeToAlertDetails.replace(`:alertId`, alert.id)); | ||
}; | ||
|
||
const getAlertStatusErrorReason = () => { | ||
if (alert.executionStatus.error) { | ||
return alert.executionStatus.error.reason; | ||
} else { | ||
return 'unknown'; | ||
} | ||
}; | ||
|
||
return ( | ||
<EuiPage> | ||
<EuiPageBody> | ||
|
@@ -275,6 +286,48 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({ | |
</EuiFlexGroup> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
{!dissmissAlertErrors && alert.executionStatus.status === 'error' ? ( | ||
<EuiFlexGroup> | ||
<EuiFlexItem> | ||
<EuiCallOut | ||
color="danger" | ||
data-test-subj="alertErrorBanner" | ||
size="s" | ||
title={ | ||
<FormattedMessage | ||
id="xpack.triggersActionsUI.sections.alertDetails.attentionBannerTitle" | ||
defaultMessage="This alert has an error caused by the {errorReason} reason." | ||
YulNaumenko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
values={{ | ||
errorReason: getAlertStatusErrorReason(), | ||
}} | ||
/> | ||
} | ||
iconType="alert" | ||
> | ||
<EuiTitle size="m"> | ||
<h3> | ||
<EuiTextColor color="danger"> | ||
<FormattedMessage | ||
id="xpack.triggersActionsUI.sections.alertDetails.alertErrorMessageTitle" | ||
defaultMessage="Error message:" | ||
/> | ||
</EuiTextColor> | ||
</h3> | ||
</EuiTitle> | ||
<EuiText size="s" color="danger" data-test-subj="alertErrorMessageText"> | ||
{alert.executionStatus.error?.message} | ||
</EuiText> | ||
<EuiSpacer size="s" /> | ||
<EuiButton color="danger" onClick={() => setDissmissAlertErrors(true)}> | ||
<FormattedMessage | ||
id="xpack.triggersActionsUI.sections.alertDetails.dismissButtonTitle" | ||
defaultMessage="Dismiss" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mdefazio did we decide to keep or remove the dismiss button? When dismissing, the banner returns next time I refresh the page. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's coming back after each refresh, then I don't think we include the dismiss option. It would be more annoying to dismiss it and it keep coming back as opposed to always seeing and not being able to do anything with it (until the errors are fixed) |
||
/> | ||
</EuiButton> | ||
</EuiCallOut> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
) : null} | ||
<EuiFlexGroup> | ||
<EuiFlexItem> | ||
{alert.enabled ? ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React, { useEffect, useState } from 'react'; | ||
import { FormattedMessage } from '@kbn/i18n/react'; | ||
import { | ||
EuiFilterGroup, | ||
EuiPopover, | ||
EuiFilterButton, | ||
EuiFilterSelectItem, | ||
EuiHealth, | ||
} from '@elastic/eui'; | ||
import { | ||
AlertExecutionStatuses, | ||
AlertExecutionStatusValues, | ||
} from '../../../../../../alerts/common'; | ||
|
||
interface AlertStatusFilterProps { | ||
selectedStatuses: string[]; | ||
onChange?: (selectedAlertStatusesIds: string[]) => void; | ||
} | ||
|
||
export const AlertStatusFilter: React.FunctionComponent<AlertStatusFilterProps> = ({ | ||
selectedStatuses, | ||
onChange, | ||
}: AlertStatusFilterProps) => { | ||
const [selectedValues, setSelectedValues] = useState<string[]>(selectedStatuses); | ||
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false); | ||
|
||
useEffect(() => { | ||
if (onChange) { | ||
onChange(selectedValues); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [selectedValues]); | ||
|
||
useEffect(() => { | ||
setSelectedValues(selectedStatuses); | ||
}, [selectedStatuses]); | ||
|
||
return ( | ||
<EuiFilterGroup> | ||
<EuiPopover | ||
isOpen={isPopoverOpen} | ||
closePopover={() => setIsPopoverOpen(false)} | ||
button={ | ||
<EuiFilterButton | ||
iconType="arrowDown" | ||
hasActiveFilters={selectedValues.length > 0} | ||
numActiveFilters={selectedValues.length} | ||
numFilters={selectedValues.length} | ||
onClick={() => setIsPopoverOpen(!isPopoverOpen)} | ||
> | ||
<FormattedMessage | ||
id="xpack.triggersActionsUI.sections.alertsList.alertStatusFilterLabel" | ||
defaultMessage="Status" | ||
/> | ||
</EuiFilterButton> | ||
} | ||
> | ||
<div className="euiFilterSelect__items"> | ||
{[...AlertExecutionStatusValues].sort().map((item: AlertExecutionStatuses) => { | ||
const healthColor = getHealthColor(item); | ||
return ( | ||
<EuiFilterSelectItem | ||
key={item} | ||
style={{ textTransform: 'capitalize' }} | ||
onClick={() => { | ||
const isPreviouslyChecked = selectedValues.includes(item); | ||
if (isPreviouslyChecked) { | ||
setSelectedValues(selectedValues.filter((val) => val !== item)); | ||
} else { | ||
setSelectedValues(selectedValues.concat(item)); | ||
} | ||
}} | ||
checked={selectedValues.includes(item) ? 'on' : undefined} | ||
> | ||
<EuiHealth color={healthColor}>{item}</EuiHealth> | ||
</EuiFilterSelectItem> | ||
); | ||
})} | ||
</div> | ||
</EuiPopover> | ||
</EuiFilterGroup> | ||
); | ||
}; | ||
|
||
export function getHealthColor(status: AlertExecutionStatuses) { | ||
switch (status) { | ||
case 'active': | ||
return 'primary'; | ||
case 'error': | ||
return 'danger'; | ||
case 'ok': | ||
return 'subdued'; | ||
case 'pending': | ||
return 'success'; | ||
default: | ||
return 'warning'; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL! Didn't realize you could do the ORs like this in KQL! Of course, I don't know KQL very well :-)
Actually, I thought KQL was sensitive to the case, and it had to be
OR
and notor
, but maybe the rules are different outside of parenthesis ... or I'm just wrong on the case sensitivity.