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:
parent
f5065462c9
commit
1ece740030
8 changed files with 131 additions and 51 deletions
|
|
@ -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)
|
||||
|
|
|
|||
32
grafana-plugin/src/assets/img/error.svg
Normal file
32
grafana-plugin/src/assets/img/error.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface PageBaseState {
|
|||
export interface PageErrorData {
|
||||
isNotFoundError?: boolean;
|
||||
isWrongTeamError?: boolean;
|
||||
isUnknownError?: boolean;
|
||||
wrongTeamNoPermissions?: boolean;
|
||||
switchToTeam?: { name: string; id: string };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue