Render alert group action buttons even if getting AG data fails (#3746)

# What this PR does

Render alert group action buttons even if getting AG data fails

## Which issue(s) this PR fixes

https://github.com/grafana/oncall-private/issues/2383

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [ ] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
This commit is contained in:
Maxim Mordasov 2024-02-02 10:56:27 +03:00 committed by GitHub
parent f5065462c9
commit 1ece740030
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 131 additions and 51 deletions

View file

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Render alert group action buttons even if getting AG data fails ([#2383](https://github.com/grafana/oncall-private/issues/2383))
- Enable Grafana Alerting V2 feature flag by default
## v1.3.98 (2024-02-01)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,7 +1,7 @@
import { PageErrorData } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
export function initErrorDataState(): Partial<PageErrorData> {
return { isWrongTeamError: false, wrongTeamNoPermissions: false };
return { isUnknownError: false, isWrongTeamError: false, wrongTeamNoPermissions: false };
}
export function getWrongTeamResponseInfo({ response }): Partial<PageErrorData> {
@ -18,5 +18,5 @@ export function getWrongTeamResponseInfo({ response }): Partial<PageErrorData> {
}
}
return { isNotFoundError: true };
return { isUnknownError: true };
}

View file

@ -18,6 +18,7 @@ export interface PageBaseState {
export interface PageErrorData {
isNotFoundError?: boolean;
isWrongTeamError?: boolean;
isUnknownError?: boolean;
wrongTeamNoPermissions?: boolean;
switchToTeam?: { name: string; id: string };
}

View file

@ -144,16 +144,16 @@ export function renderRelatedUsers(incident: Alert, isFull = false) {
);
}
export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key: string]: any }) {
if (incident.root_alert_group) {
export function getActionButtons(incident: AlertType, callbacks: { [key: string]: any }, allSecondary = false) {
const { onResolve, onUnresolve, onAcknowledge, onUnacknowledge, onSilence, onUnsilence } = callbacks;
if (incident?.root_alert_group) {
return null;
}
const { onResolve, onUnresolve, onAcknowledge, onUnacknowledge, onSilence, onUnsilence } = callbacks;
const resolveButton = (
<WithPermissionControlTooltip key="resolve" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident.loading} onClick={onResolve} variant="primary">
<Button disabled={incident?.loading} onClick={onResolve} variant={allSecondary ? 'secondary' : 'primary'}>
Resolve
</Button>
</WithPermissionControlTooltip>
@ -161,15 +161,15 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key
const unacknowledgeButton = (
<WithPermissionControlTooltip key="unacknowledge" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident.loading} onClick={onUnacknowledge} variant="secondary">
<Button disabled={incident?.loading} onClick={onUnacknowledge} variant="secondary">
Unacknowledge
</Button>
</WithPermissionControlTooltip>
);
const unresolveButton = (
<WithPermissionControlTooltip key="unacknowledge" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident.loading} onClick={onUnresolve} variant="primary">
<WithPermissionControlTooltip key="unresolve" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident?.loading} onClick={onUnresolve} variant={allSecondary ? 'secondary' : 'primary'}>
Unresolve
</Button>
</WithPermissionControlTooltip>
@ -177,31 +177,37 @@ export function getActionButtons(incident: AlertType, cx: any, callbacks: { [key
const acknowledgeButton = (
<WithPermissionControlTooltip key="acknowledge" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident.loading} onClick={onAcknowledge} variant="secondary">
<Button disabled={incident?.loading} onClick={onAcknowledge} variant="secondary">
Acknowledge
</Button>
</WithPermissionControlTooltip>
);
const silenceButton = (
<WithPermissionControlTooltip key="silence" userAction={UserActions.AlertGroupsWrite}>
<SilenceButtonCascader disabled={incident?.loading} onSelect={onSilence} />
</WithPermissionControlTooltip>
);
const unsilenceButton = (
<WithPermissionControlTooltip key="unsilence" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident?.loading} variant="secondary" onClick={onUnsilence}>
Unsilence
</Button>
</WithPermissionControlTooltip>
);
if (incident?.status === undefined) {
// to render all buttons if status unknown
return [acknowledgeButton, unacknowledgeButton, resolveButton, unresolveButton, silenceButton, unsilenceButton];
}
const buttons = [];
if (incident.status === IncidentStatus.Silenced) {
buttons.push(
<WithPermissionControlTooltip key="silence" userAction={UserActions.AlertGroupsWrite}>
<Button disabled={incident.loading} variant="secondary" onClick={onUnsilence}>
Unsilence
</Button>
</WithPermissionControlTooltip>
);
buttons.push(unsilenceButton);
} else if (incident.status !== IncidentStatus.Resolved) {
buttons.push(
<SilenceButtonCascader
className={cx('silence-button-inline')}
key="silence"
disabled={incident.loading}
onSelect={onSilence}
/>
);
buttons.push(silenceButton);
}
if (!incident.resolved && !incident.acknowledged) {

View file

@ -92,6 +92,16 @@
text-align: center;
}
.alert-group-stub {
margin: 24px auto;
width: 520px;
text-align: center;
}
.alert-group-stub-divider {
width: 520px;
}
.timeline-icon-background {
width: 28px;
height: 28px;

View file

@ -14,6 +14,7 @@ import {
Field,
Modal,
Tooltip,
Divider,
} from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
@ -24,6 +25,7 @@ import { RouteComponentProps, withRouter } from 'react-router-dom';
import reactStringReplace from 'react-string-replace';
import { OnCallPluginExtensionPoints } from 'types';
import errorSVG from 'assets/img/error.svg';
import Collapse from 'components/Collapse/Collapse';
import { ExtensionLinkDropdown } from 'components/ExtensionLinkMenu/ExtensionLinkDropdown';
import Block from 'components/GBlock/Block';
@ -43,14 +45,7 @@ import { prepareForUpdate } from 'containers/AddResponders/AddResponders.helpers
import { UserResponder } from 'containers/AddResponders/AddResponders.types';
import AttachIncidentForm from 'containers/AttachIncidentForm/AttachIncidentForm';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import {
Alert as AlertType,
Alert,
AlertAction,
TimeLineItem,
TimeLineRealm,
GroupedAlert,
} from 'models/alertgroup/alertgroup.types';
import { Alert, AlertAction, TimeLineItem, TimeLineRealm, GroupedAlert } from 'models/alertgroup/alertgroup.types';
import { ResolutionNoteSourceTypesToDisplayName } from 'models/resolution_note/resolution_note.types';
import { User } from 'models/user/user.types';
import { IncidentDropdown } from 'pages/incidents/parts/IncidentDropdown';
@ -133,12 +128,31 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
} = this.props;
const { errorData, showIntegrationSettings, showAttachIncidentForm } = this.state;
const { isNotFoundError, isWrongTeamError } = errorData;
const { isNotFoundError, isWrongTeamError, isUnknownError } = errorData;
// const { alertReceiveChannelStore } = store;
const { alerts } = store.alertGroupStore;
const incident = alerts.get(id);
if (isUnknownError) {
return (
<AlertGroupStub
buttons={getActionButtons(
incident,
{
onResolve: this.getOnActionButtonClick(id, AlertAction.Resolve),
onUnacknowledge: this.getOnActionButtonClick(id, AlertAction.unAcknowledge),
onUnresolve: this.getOnActionButtonClick(id, AlertAction.unResolve),
onAcknowledge: this.getOnActionButtonClick(id, AlertAction.Acknowledge),
onSilence: this.getSilenceClickHandler(id),
onUnsilence: this.getUnsilenceClickHandler(id),
},
true
)}
/>
);
}
if (!incident && !isNotFoundError && !isWrongTeamError) {
return (
<div className={cx('root')}>
@ -332,8 +346,8 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
onUnacknowledge={this.getOnActionButtonClick(incident.pk, AlertAction.unAcknowledge)}
onUnresolve={this.getOnActionButtonClick(incident.pk, AlertAction.unResolve)}
onAcknowledge={this.getOnActionButtonClick(incident.pk, AlertAction.Acknowledge)}
onSilence={this.getSilenceClickHandler(incident)}
onUnsilence={this.getUnsilenceClickHandler(incident)}
onSilence={this.getSilenceClickHandler(incident.pk)}
onUnsilence={this.getUnsilenceClickHandler(incident.pk)}
/>
</div>
@ -413,13 +427,13 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
</div>
<HorizontalGroup justify="space-between" className={cx('buttons-row')}>
<HorizontalGroup>
{getActionButtons(incident, cx, {
{getActionButtons(incident, {
onResolve: this.getOnActionButtonClick(incident.pk, AlertAction.Resolve),
onUnacknowledge: this.getOnActionButtonClick(incident.pk, AlertAction.unAcknowledge),
onUnresolve: this.getOnActionButtonClick(incident.pk, AlertAction.unResolve),
onAcknowledge: this.getOnActionButtonClick(incident.pk, AlertAction.Acknowledge),
onSilence: this.getSilenceClickHandler(incident),
onUnsilence: this.getUnsilenceClickHandler(incident),
onSilence: this.getSilenceClickHandler(incident.pk),
onUnsilence: this.getUnsilenceClickHandler(incident.pk),
})}
{incident.grafana_incident_id === null && (
<PluginBridge plugin={SupportedPlugin.Incident}>
@ -615,7 +629,7 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
};
};
getOnActionButtonClick = (incidentId: string, action: AlertAction) => {
getOnActionButtonClick = (incidentId: Alert['pk'], action: AlertAction) => {
const { store } = this.props;
return (e: SyntheticEvent) => {
@ -625,23 +639,23 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
};
};
getSilenceClickHandler = (alert: AlertType) => {
getSilenceClickHandler = (incidentId: Alert['pk']) => {
const { store } = this.props;
return (value: number) => {
return store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.Silence, false, {
return store.alertGroupStore.doIncidentAction(incidentId, AlertAction.Silence, false, {
delay: value,
});
};
};
getUnsilenceClickHandler = (alert: AlertType) => {
getUnsilenceClickHandler = (incidentId: Alert['pk']) => {
const { store } = this.props;
return (event: any) => {
event.stopPropagation();
return store.alertGroupStore.doIncidentAction(alert.pk, AlertAction.unSilence, false);
return store.alertGroupStore.doIncidentAction(incidentId, AlertAction.unSilence, false);
};
};
@ -825,4 +839,26 @@ function AttachedIncidentsList({
);
}
const AlertGroupStub = ({ buttons }: { buttons: React.ReactNode }) => {
return (
<div className={cx('alert-group-stub')}>
<VerticalGroup align="center" spacing="md">
<img src={errorSVG} alt="" />
<Text.Title level={3}>An unexpected error happened</Text.Title>
<Text type="secondary">
OnCall is not able to receive any information about the current Alert Group. It's unknown if it's firing,
acknowledged, silenced, or resolved.
</Text>
<div className={cx('alert-group-stub-divider')}>
<Divider />
</div>
<Text type="secondary">Meanwhile, you could try changing the status of this Alert Group:</Text>
<HorizontalGroup wrap justify="center">
{buttons}
</HorizontalGroup>
</VerticalGroup>
</div>
);
};
export default withRouter(withMobXProviderContext(IncidentPage));

View file

@ -45,12 +45,6 @@
color: var(--secondary-text-color);
}
.silence-button-inline {
font-size: 12px;
height: 24px;
margin-right: 0;
}
.pagination {
width: 100%;
margin-top: 20px;