Skip to content

Commit

Permalink
Improve direct user paging (#1471)
Browse files Browse the repository at this point in the history
# What this PR does

Direct user paging feature improvements

## Which issue(s) this PR fixes

#1358

## Checklist

- [ ] Tests updated
- [ ] Documentation added
- [ ] `CHANGELOG.md` updated
  • Loading branch information
Maxim Mordasov authored Mar 13, 2023
1 parent 15f6898 commit 82a9f8a
Show file tree
Hide file tree
Showing 12 changed files with 278 additions and 104 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Enable web overrides for Terraform-based schedules
- Direct user paging improvements ([1358](https://github.com/grafana/oncall/issues/1358))

## v1.1.36 (2023-03-09)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FormItem, FormItemType } from 'components/GForm/GForm.types';

export const manualAlertFormConfig: { name: string; fields: FormItem[] } = {
name: 'Manual Alert Group',
fields: [
{
name: 'title',
type: FormItemType.Input,
label: 'Title',
validation: { required: true },
},
{
name: 'message',
type: FormItemType.TextArea,
label: 'Description',
validation: { required: true },
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import cn from 'classnames/bind';

import Block from 'components/GBlock/Block';
import GForm from 'components/GForm/GForm';
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
import Text from 'components/Text/Text';
import EscalationVariants from 'containers/EscalationVariants/EscalationVariants';
import { prepareForUpdate } from 'containers/EscalationVariants/EscalationVariants.helpers';
import { Alert } from 'models/alertgroup/alertgroup.types';
import { useStore } from 'state/useStore';

import { manualAlertFormConfig } from './ManualAlertGroup.config';

import styles from './ManualAlertGroup.module.css';

interface ManualAlertGroupProps {
Expand All @@ -21,23 +22,6 @@ interface ManualAlertGroupProps {

const cx = cn.bind(styles);

const manualAlertFormConfig: { name: string; fields: FormItem[] } = {
name: 'Manual Alert Group',
fields: [
{
name: 'title',
type: FormItemType.Input,
label: 'Title',
validation: { required: true },
},
{
name: 'message',
type: FormItemType.TextArea,
label: 'Describe what is going on',
},
],
};

const ManualAlertGroup: FC<ManualAlertGroupProps> = (props) => {
const store = useStore();
const [userResponders, setUserResponders] = useState([]);
Expand Down
4 changes: 3 additions & 1 deletion grafana-plugin/src/components/PluginLink/PluginLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ interface PluginLinkProps {
wrap?: boolean;
children: any;
query?: Record<string, any>;
target?: string;
}

const cx = cn.bind(styles);

const PluginLink: FC<PluginLinkProps> = (props) => {
const { children, query, disabled, className, wrap = true } = props;
const { children, query, disabled, className, wrap = true, target } = props;

const newPath = useMemo(() => getPathFromQueryParams(query), [query]);

Expand All @@ -35,6 +36,7 @@ const PluginLink: FC<PluginLinkProps> = (props) => {

return (
<Link
target={target}
onClick={handleClick}
className={cx('root', className, { 'no-wrap': !wrap, root_disabled: disabled })}
to={newPath}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@
overflow: scroll;
}

.schedule-table {
.table {
height: 120px;
overflow: auto;

& tr:hover {
background: var(--background-secondary) !important;
}

& tbody tr:nth-child(odd) {
background: unset;
}
}

.responders-filters {
Expand All @@ -32,6 +40,8 @@

.responder-item {
cursor: pointer;
width: 280px;
overflow: hidden;
}

.body {
Expand All @@ -43,12 +53,12 @@
margin-bottom: 20px;
width: 100%;

& > li .trash-button {
& > li .hover-button {
display: none;
}

& > li:hover .trash-button {
display: block;
& > li:hover .hover-button {
display: inline-flex;
}

& > li {
Expand Down Expand Up @@ -79,3 +89,17 @@
background: #299c46;
}
}

.radio-buttons {
margin: 8px;
}

.select {
width: 150px !important;
}

.responder-name {
max-width: 250px;
overflow: hidden;
white-space: nowrap;
}
158 changes: 114 additions & 44 deletions grafana-plugin/src/containers/EscalationVariants/EscalationVariants.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import React, { useState, useCallback } from 'react';

import { SelectableValue } from '@grafana/data';
import { ToolbarButton, ButtonGroup, HorizontalGroup, Icon, Select, IconButton, Label } from '@grafana/ui';
import { HorizontalGroup, Icon, Select, IconButton, Label, Tooltip, Button } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { observer } from 'mobx-react';

import Avatar from 'components/Avatar/Avatar';
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import UserWarning from 'containers/UserWarningModal/UserWarning';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { getTzOffsetString } from 'models/timezone/timezone.helpers';
import { User } from 'models/user/user.types';
import { UserActions } from 'utils/authorization';

Expand All @@ -24,7 +23,7 @@ const cx = cn.bind(styles);
export interface EscalationVariantsProps {
onUpdateEscalationVariants: (data: any) => void;
value: { scheduleResponders; userResponders };
variant?: 'default' | 'primary';
variant?: 'secondary' | 'primary';
hideSelected?: boolean;
}

Expand Down Expand Up @@ -124,29 +123,17 @@ const EscalationVariants = observer(
</>
)}
<div className={cx('assign-responders-button')}>
<ButtonGroup>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<ToolbarButton
icon="users-alt"
variant={variant}
onClick={() => {
setShowEscalationVariants(true);
}}
>
Add responders
</ToolbarButton>
</WithPermissionControlTooltip>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<ToolbarButton
isOpen={false}
narrow
variant={variant}
onClick={() => {
setShowEscalationVariants(true);
}}
/>
</WithPermissionControlTooltip>
</ButtonGroup>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<Button
icon="users-alt"
variant={variant}
onClick={() => {
setShowEscalationVariants(true);
}}
>
Add responders
</Button>
</WithPermissionControlTooltip>
</div>
{showEscalationVariants && (
<EscalationVariantsPopup
Expand All @@ -170,7 +157,17 @@ const EscalationVariants = observer(
onUserSelect={(user: User) => {
onUpdateEscalationVariants({
...value,
userResponders: [...value.userResponders, { type: ResponderType.User, data: user, important: false }],
userResponders: [
...value.userResponders,
{
type: ResponderType.User,
data: user,
important:
user.notification_chain_verbal.important && !user.notification_chain_verbal.default
? true
: false,
},
],
});
}}
/>
Expand All @@ -188,20 +185,72 @@ const UserResponder = ({ important, data, onImportantChange, handleDelete }) =>
<div className={cx('timeline-icon-background', { 'timeline-icon-background--green': true })}>
<Avatar size="big" src={data?.avatar} />
</div>
<Text>
{data?.username} ({getTzOffsetString(dayjs().tz(data?.timezone))})
</Text>
<Select
isSearchable={false}
value={Number(important)}
options={[
{ value: 1, label: 'Important' },
{ value: 0, label: 'Default' },
]}
onChange={onImportantChange}
<Text className={cx('responder-name')}>{data?.username}</Text>
{data.notification_chain_verbal.default || data.notification_chain_verbal.important ? (
<HorizontalGroup>
<Text type="secondary">by</Text>
<Select
className={cx('select')}
isSearchable={false}
value={Number(important)}
options={[
{
value: 0,
label: 'Default',
description: 'Use "Default notifications" from user\'s personal settings',
},
{
value: 1,
label: 'Important',
description: 'Use "Important notifications" from user\'s personal settings',
},
]}
// @ts-ignore
isOptionDisabled={({ value }) =>
(value === 0 && !data.notification_chain_verbal.default) ||
(value === 1 && !data.notification_chain_verbal.important)
}
getOptionLabel={({ value, label }) => {
return (
<Text
type={
(value === 0 && !data.notification_chain_verbal.default) ||
(value === 1 && !data.notification_chain_verbal.important)
? 'disabled'
: 'primary'
}
>
{label}
</Text>
);
}}
onChange={onImportantChange}
/>
<Text type="secondary">notification chain</Text>
</HorizontalGroup>
) : (
<HorizontalGroup>
<Tooltip content="User doesn't have configured notification chains">
<Icon name="exclamation-triangle" style={{ color: 'var(--error-text-color)' }} />
</Tooltip>
</HorizontalGroup>
)}
</HorizontalGroup>
<HorizontalGroup>
<PluginLink className={cx('hover-button')} target="_blank" query={{ page: 'users', id: data.pk }}>
<IconButton
tooltip="Open user profile in new tab"
style={{ color: 'var(--always-gray)' }}
name="external-link-alt"
/>
</PluginLink>
<IconButton
tooltip="Remove responder"
className={cx('hover-button')}
name="trash-alt"
onClick={handleDelete}
/>
</HorizontalGroup>
<IconButton className={cx('trash-button')} name="trash-alt" onClick={handleDelete} />
</HorizontalGroup>
</li>
);
Expand All @@ -215,18 +264,39 @@ const ScheduleResponder = ({ important, data, onImportantChange, handleDelete })
<div className={cx('timeline-icon-background')}>
<Icon size="lg" name="calendar-alt" />
</div>
<Text>{data.name}</Text>
<Text className={cx('responder-name')}>{data.name}</Text>
<Text type="secondary">by</Text>
<Select
className={cx('select')}
isSearchable={false}
value={Number(important)}
options={[
{ value: 1, label: 'Important' },
{ value: 0, label: 'Default' },
{ value: 0, label: 'Default', description: 'Use "Default notifications" from users personal settings' },
{
value: 1,
label: 'Important',
description: 'Use "Important notifications" from users personal settings',
},
]}
onChange={onImportantChange}
/>
<Text type="secondary">notification policies</Text>
</HorizontalGroup>
<HorizontalGroup>
<PluginLink className={cx('hover-button')} target="_blank" query={{ page: 'schedules', id: data.id }}>
<IconButton
tooltip="Open schedule in new tab"
style={{ color: 'var(--always-gray)' }}
name="external-link-alt"
/>
</PluginLink>
<IconButton
className={cx('hover-button')}
tooltip="Remove responder"
name="trash-alt"
onClick={handleDelete}
/>
</HorizontalGroup>
<IconButton className={cx('trash-button')} name="trash-alt" onClick={handleDelete} />
</HorizontalGroup>
</li>
);
Expand Down
Loading

0 comments on commit 82a9f8a

Please sign in to comment.