Skip to content

Commit

Permalink
Direct paging improvements (#2537)
Browse files Browse the repository at this point in the history
# What this PR does

- Deprecates `/oncall` Slack command in favour of `/esalate` (direct
paging) + fixes a regression bug in both commands
- Unifies direct paging UX across Slack & Web UI (or at least makes an
attempt to make things more similar). Kudos to @iskhakov for all the
great work on this recently!
- A bunch of minor changes that hopefully make direct paging more usable
- TODO: documentation updates will be added in a separate PR

## Screenshots

### No issues scenario

Slack:

<img width="522" alt="Screenshot 2023-07-14 at 23 53 11"
src="https://github.com/grafana/oncall/assets/20116910/ec15a18f-d817-4177-b1f2-6b89d79bb361">


Web UI: 

<img width="1172" alt="Screenshot 2023-07-14 at 23 52 25"
src="https://github.com/grafana/oncall/assets/20116910/813f967c-2fdd-4868-9287-487dbfa7cea6">


### Not configured scenario

Slack:

<img width="519" alt="Screenshot 2023-07-14 at 23 45 22"
src="https://github.com/grafana/oncall/assets/20116910/932fa05c-81ea-42ca-be80-41b05f767d3e">

Web UI:

<img width="1172" alt="Screenshot 2023-07-14 at 23 47 31"
src="https://github.com/grafana/oncall/assets/20116910/6bcb07e4-2e50-4120-9fac-be8b0277e181">

### `/oncall` deprecation warning

<img width="521" alt="Screenshot 2023-07-17 at 10 31 56"
src="https://github.com/grafana/oncall/assets/20116910/4ff28337-1693-4af0-81d9-9eda90099c1b">


## Which issue(s) this PR fixes

#2442

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
  • Loading branch information
vstpme authored Jul 17, 2023
1 parent 8383835 commit 87ef68c
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 65 deletions.
27 changes: 27 additions & 0 deletions integration-tests/alerts/directPaging.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { test } from '../fixtures';
import { clickButton, fillInInput, selectDropdownValue } from '../utils/forms';
import { goToOnCallPage } from "../utils/navigation";
import { verifyAlertGroupTitleAndMessageContainText } from "../utils/alertGroup";

test('we can create an alert group for default team', async ({ adminRolePage }) => {
const { page } = adminRolePage;

await goToOnCallPage(page, 'alert-groups');
await clickButton({ page, buttonText: 'New alert group' });

await fillInInput(page, 'input[name="title"]', "Help me!");
await fillInInput(page, 'textarea[name="message"]', "Help me please!");

await selectDropdownValue({
page,
selectType: 'grafanaSelect',
placeholderText: "Select team",
value: "No team",
});

await clickButton({ page, buttonText: 'Create' });

// Check we are redirected to the alert group page
await page.waitForURL('**/alert-groups/I*'); // Alert group IDs always start with "I"
await verifyAlertGroupTitleAndMessageContainText(page, "Help me!", "Help me please!")
});
12 changes: 11 additions & 1 deletion integration-tests/utils/alertGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const filterAlertGroupsTableByIntegrationAndGoToDetailPage = async (
throw new Error('we were not able to properly filter the alert groups table by integration');
}

await goToOnCallPage(page, 'incidents');
await goToOnCallPage(page, 'alert-groups');

// filter by integration
const selectElement = await selectDropdownValue({
Expand Down Expand Up @@ -100,3 +100,13 @@ export const verifyThatAlertGroupIsTriggered = async (

expect(await incidentTimelineContainsStep(page, triggeredStepText)).toBe(true);
};


export const verifyAlertGroupTitleAndMessageContainText = async (
page: Page,
title: string,
message: string
): Promise<void> => {
await expect(page.getByTestId('incident-title')).toContainText(title);
await expect(page.getByTestId('incident-message')).toContainText(message);
};
2 changes: 1 addition & 1 deletion integration-tests/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Page, Response } from '@playwright/test';
import { BASE_URL } from './constants';

type GrafanaPage = '/plugins/grafana-oncall-app';
type OnCallPage = 'incidents' | 'integrations' | 'escalations' | 'schedules' | 'users';
type OnCallPage = 'alert-groups' | 'integrations' | 'escalations' | 'schedules' | 'users';

const _goToPage = (page: Page, url = ''): Promise<Response> => page.goto(`${BASE_URL}${url}`);

Expand Down
4 changes: 2 additions & 2 deletions src/components/ManualAlertGroup/ManualAlertGroup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const manualAlertFormConfig: { name: string; fields: FormItem[] } = {
{
name: 'message',
type: FormItemType.TextArea,
label: 'Description',
validation: { required: true },
label: 'Message (optional)',
validation: { required: false },
},
],
};
102 changes: 47 additions & 55 deletions src/components/ManualAlertGroup/ManualAlertGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
);

const DirectPagingIntegrationVariants = ({ selectedTeamId, selectedTeamDirectPaging, chatOpsAvailableChannels }) => {
const escalationChainsExist = selectedTeamDirectPaging?.connected_escalations_chains_count === 0;
const escalationChainsExist = selectedTeamDirectPaging?.connected_escalations_chains_count !== 0;

return (
<VerticalGroup>
Expand All @@ -122,41 +122,32 @@ const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
<LoadingPlaceholder text="Loading..." />
) : selectedTeamDirectPaging ? (
<VerticalGroup>
<Label>Team will be notified according to the integration settings:</Label>
<Label>Integration to be used for notification</Label>
<ul className={cx('responders-list')}>
<li>
<HorizontalGroup justify="space-between">
<HorizontalGroup>
{escalationChainsExist && (
<Tooltip content="Integration doesn't have connected escalation policies">
<Icon name="exclamation-triangle" style={{ color: 'var(--warning-text-color)' }} />
</Tooltip>
)}
<Text>{selectedTeamDirectPaging.verbal_name}</Text>
</HorizontalGroup>
<HorizontalGroup>
<Text type="secondary">Team:</Text>
<TeamName team={store.grafanaTeamStore.items[selectedTeamId]} />
</HorizontalGroup>
<HorizontalGroup>
{chatOpsAvailableChannels && (
<>
{chatOpsAvailableChannels.map(
(chatOpsChannel: { name: string; icon: IconName }, chatOpsIndex) => (
<div key={`${chatOpsChannel.name}-${chatOpsIndex}`}>
{chatOpsChannel.icon && <Icon name={chatOpsChannel.icon} />}
<Text type="primary">{chatOpsChannel.name || ''}</Text>
</div>
)
)}
{chatOpsAvailableChannels && (
<Tooltip content="Alert group will be posted to these chatops channels according to integration configuration">
<Icon name="info-circle" />
</Tooltip>
)}
</>
)}
</HorizontalGroup>
{chatOpsAvailableChannels.length && (
<HorizontalGroup>
{chatOpsAvailableChannels.map(
(chatOpsChannel: { name: string; icon: IconName }, chatOpsIndex) => (
<div key={`${chatOpsChannel.name}-${chatOpsIndex}`}>
{chatOpsChannel.icon && <Icon name={chatOpsChannel.icon} />}
<Text type="primary">{chatOpsChannel.name || ''}</Text>
</div>
)
)}
<Tooltip content="Alert group will be posted to these ChatOps channels">
<Icon name="info-circle" />
</Tooltip>
</HorizontalGroup>
)}
<HorizontalGroup>
<PluginLink target="_blank" query={{ page: 'integrations', id: selectedTeamDirectPaging.id }}>
<IconButton
Expand All @@ -169,29 +160,41 @@ const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
</HorizontalGroup>
</li>
</ul>

{(escalationChainsExist || !chatOpsAvailableChannels) && (
<Alert severity="warning" title="Possible notification miss">
{!escalationChainsExist && (
<Alert severity="warning" title="Direct paging integration not configured">
<VerticalGroup>
{escalationChainsExist && (
<Text>
Integration doesn't have connected escalation policies. Consider adding responders manually by
user or by email
</Text>
)}
{!chatOpsAvailableChannels && (
<Text>Integration doesn't have connected ChatOps channels in messengers.</Text>
)}
<Text>
The direct paging integration for the selected team has no escalation chains configured.
<br />
If you proceed with the alert group, the team likely will not be notified. <br />
<a
href={'https://grafana.com/docs/oncall/latest/integrations/manual/'}
target="_blank"
rel="noreferrer"
className={cx('link')}
>
<Text type="link">Learn more.</Text>
</a>
</Text>
</VerticalGroup>
</Alert>
)}
</VerticalGroup>
) : (
<Alert severity="warning" title={"This team doesn't have the the Direct Paging integration yet"}>
<Alert severity="warning" title={'Direct paging integration missing'}>
<HorizontalGroup>
<Text>
Empty integration for this team will be created automatically. Consider selecting responders by
schedule or user below
The selected team doesn't have a direct paging integration configured and will not be notified. <br />
If you proceed with the alert group, an empty direct paging integration will be created automatically
for the team. <br />
<a
href={'https://grafana.com/docs/oncall/latest/integrations/manual/'}
target="_blank"
rel="noreferrer"
className={cx('link')}
>
<Text type="link">Learn more.</Text>
</a>
</Text>
</HorizontalGroup>
</Alert>
Expand All @@ -200,22 +203,11 @@ const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
);
};

const submitButtonDisabled = !(
selectedTeamId &&
(selectedTeamDirectPaging || userResponders.length || scheduleResponders.length)
);

return (
<Drawer
scrollableContent
title="Create manual alert group (Direct Paging)"
onClose={onHide}
closeOnMaskClick={false}
width="70%"
>
<Drawer scrollableContent title="Create Alert Group" onClose={onHide} closeOnMaskClick={false} width="70%">
<VerticalGroup>
<GForm form={manualAlertFormConfig} data={data} onSubmit={handleFormSubmit} />
<Field label="Select team you want to notify">
<Field label="Team to notify">
<GrafanaTeamSelect withoutModal onSelect={onUpdateSelectedTeam} />
</Field>
<DirectPagingIntegrationVariants
Expand All @@ -233,7 +225,7 @@ const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
<Button variant="secondary" onClick={onHide}>
Cancel
</Button>
<Button type="submit" form={manualAlertFormConfig.name} disabled={submitButtonDisabled}>
<Button type="submit" form={manualAlertFormConfig.name} disabled={!selectedTeamId}>
Create
</Button>
</HorizontalGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/containers/EscalationVariants/EscalationVariants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const EscalationVariants = observer(
<div className={cx('body')}>
{!hideSelected && Boolean(value.userResponders.length || value.scheduleResponders.length) && (
<>
<Label>Additional Responders will be notified immediately:</Label>
<Label>Additional responders will be notified immediately:</Label>
<ul className={cx('responders-list')}>
{value.userResponders.map((responder, index) => (
<UserResponder
Expand All @@ -127,7 +127,7 @@ const EscalationVariants = observer(
</>
)}
<div className={cx('assign-responders-button')}>
{withLabels && <Label>Assign additional responders from other teams (by user or by schedule)</Label>}
{withLabels && <Label>Additional responders (optional)</Label>}
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<Button
icon="users-alt"
Expand All @@ -137,7 +137,7 @@ const EscalationVariants = observer(
setShowEscalationVariants(true);
}}
>
Invite additional responders
Notify additional responders
</Button>
</WithPermissionControlTooltip>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/containers/GrafanaTeamSelect/GrafanaTeamSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ const GrafanaTeamSelect = observer(({ onSelect, onHide, withoutModal, defaultVal

const select = (
<GSelect
showSearch
modelName="grafanaTeamStore"
displayField="name"
valueField="id"
placeholder="Select Team"
placeholder="Select team"
className={cx('select', 'control')}
value={selectedTeam}
onChange={handleTeamSelect}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/incident/Incident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ class IncidentPage extends React.Component<IncidentPageProps, IncidentPageState>
</PluginLink>
{/* @ts-ignore*/}
<HorizontalGroup align="baseline">
<Text.Title level={3}>
<Text.Title level={3} data-testid="incident-title">
#{incident.inside_organization_number} {incident.render_for_web.title}
</Text.Title>
{incident.root_alert_group && (
Expand Down Expand Up @@ -647,6 +647,7 @@ function Incident({ incident }: { incident: Alert; datetimeReference: string })
dangerouslySetInnerHTML={{
__html: sanitize(incident.render_for_web.message),
}}
data-testid="incident-message"
/>
{incident.render_for_web.image_url && <img className={cx('image')} src={incident.render_for_web.image_url} />}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/incidents/Incidents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class Incidents extends React.Component<IncidentsPageProps, IncidentsPageState>
<Text.Title level={3}>Alert Groups</Text.Title>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<Button icon="plus" onClick={this.handleOnClickEscalateTo}>
New manual alert group
New alert group
</Button>
</WithPermissionControlTooltip>
</HorizontalGroup>
Expand Down

0 comments on commit 87ef68c

Please sign in to comment.