Skip to content

Commit

Permalink
Show info about missing users in schedules (#4294)
Browse files Browse the repository at this point in the history
# What this PR does

- Don't show global notification if one of the rolling_users is a user
that doesn't exist anymore (has been deleted)
- In case user present in rolling_users has been either deleted or
his/her role has been downgraded to Viewer, show such explicit info on
UI

![image](https://github.com/grafana/oncall/assets/12073649/45cec62d-b62b-4085-b536-906ef9dbef1f)

![image](https://github.com/grafana/oncall/assets/12073649/692dea50-10c7-47e9-9371-565e21d80cfe)


## Which issue(s) this PR closes

Closes #4251

<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## 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] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
brojd authored Apr 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 0790d45 commit b5d900d
Showing 6 changed files with 66 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { ComponentProps, FC } from 'react';

import { HorizontalGroup, Icon, Tooltip } from '@grafana/ui';

interface NonExistentUserNameProps {
justify?: ComponentProps<typeof HorizontalGroup>['justify'];
userName?: string;
}

const NonExistentUserName: FC<NonExistentUserNameProps> = ({ justify = 'space-between', userName }) => (
<HorizontalGroup justify={justify}>
<span>Missing user</span>
<Tooltip content={`${userName || 'User'} } is not found or doesn't have permission to participate in the rotation`}>
<Icon name="exclamation-triangle" />
</Tooltip>
</HorizontalGroup>
);

export default NonExistentUserName;
22 changes: 16 additions & 6 deletions grafana-plugin/src/containers/RotationForm/parts/UserItem.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { COLORS } from 'styles/utils.styles';

import NonExistentUserName from 'components/NonExistentUserName/NonExistentUserName';
import { Text } from 'components/Text/Text';
import { WorkingHours } from 'components/WorkingHours/WorkingHours';
import { ApiSchemas } from 'network/oncall-api/api.types';
@@ -30,18 +31,17 @@ export const UserItem = ({ pk, shiftColor, shiftStart, shiftEnd }: UserItemProps

useEffect(() => {
if (!userStore.items[pk]) {
userStore.fetchItemById({ userPk: pk, skipIfAlreadyPending: true });
userStore.fetchItemById({ userPk: pk, skipIfAlreadyPending: true, skipErrorHandling: true });
}
}, []);

const name = userStore.items[pk]?.username;
const desc = userStore.items[pk]?.timezone;
const workingHours = userStore.items[pk]?.working_hours;
const timezone = userStore.items[pk]?.timezone;
const workingHours = userStore.items[pk]?.working_hours;
const duration = dayjs(shiftEnd).diff(dayjs(shiftStart), 'seconds');

return (
<div className={cx('user-item')} style={{ backgroundColor: shiftColor, width: '100%' }}>
const slotContent = name ? (
<>
{duration <= WEEK_IN_SECONDS && (
<WorkingHours
timezone={timezone}
@@ -52,8 +52,18 @@ export const UserItem = ({ pk, shiftColor, shiftStart, shiftEnd }: UserItemProps
/>
)}
<div className={cx('user-title')}>
<Text strong>{name}</Text> <Text className={styles.gray}>({desc})</Text>
<Text strong>{name}</Text> <Text className={styles.gray}>({timezone})</Text>
</div>
</>
) : (
<div className={cx('user-title')}>
<NonExistentUserName justify="flex-start" />
</div>
);

return (
<div className={cx('user-item')} style={{ backgroundColor: shiftColor, width: '100%' }}>
{slotContent}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -56,9 +56,8 @@
z-index: 1;
color: #fff;
font-size: 12px;
width: 100%;
font-weight: 500;
pointer-events: none;
position: absolute;
white-space: nowrap;
}

35 changes: 28 additions & 7 deletions grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ import dayjs from 'dayjs';
import { observer } from 'mobx-react';

import { Avatar } from 'components/Avatar/Avatar';
import NonExistentUserName from 'components/NonExistentUserName/NonExistentUserName';
import { RenderConditionally } from 'components/RenderConditionally/RenderConditionally';
import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types';
import { Text } from 'components/Text/Text';
import { WorkingHours } from 'components/WorkingHours/WorkingHours';
@@ -59,7 +61,7 @@ export const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {

const currentMoment = useMemo(() => dayjs(), []);

const renderEvent = (event): React.ReactElement | React.ReactElement[] => {
const renderEvent = (event: Event): React.ReactElement | React.ReactElement[] => {
if (event.shiftSwapId) {
return <ShiftSwapEvent currentMoment={currentMoment} event={event} />;
}
@@ -74,12 +76,31 @@ export const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {

if (event.is_empty) {
return (
<div
className={cx('root')}
style={{
backgroundColor: color,
}}
/>
<RenderConditionally
shouldRender={event.missing_users.length > 0}
backupChildren={
<div
className={cx('root')}
style={{
backgroundColor: color,
}}
/>
}
>
{event.missing_users.map((name) => (
<div
key={name}
className={cx('root')}
style={{
backgroundColor: color,
}}
>
<div className={cx('title')}>
<NonExistentUserName userName={name} />
</div>
</div>
))}
</RenderConditionally>
);
}

2 changes: 1 addition & 1 deletion grafana-plugin/src/models/schedule/schedule.ts
Original file line number Diff line number Diff line change
@@ -522,7 +522,7 @@ export class ScheduleStore extends BaseStore {
...this.events[scheduleId],
[type]: {
...this.events[scheduleId]?.[type],
[fromString]: layers ? layers : shifts,
[fromString]: layers || shifts,
},
},
};
2 changes: 1 addition & 1 deletion grafana-plugin/src/models/schedule/schedule.types.ts
Original file line number Diff line number Diff line change
@@ -92,7 +92,7 @@ export interface Event {
end: string;
is_empty: boolean;
is_gap: boolean;
missing_users: Array<{ display_name: ApiSchemas['User']['username']; pk: ApiSchemas['User']['pk'] }>;
missing_users: Array<ApiSchemas['User']['username']>;
priority_level: number;
shift: Pick<Shift, 'name' | 'type'> & { pk: string };
source: string;

0 comments on commit b5d900d

Please sign in to comment.