Build ‘When I am on-call’ for web UI #2915 (#3100)

# What this PR does

Polish ‘When I am on-call’ feature

## Which issue(s) this PR fixes

[Build ‘When I am on-call’ for web UI
#2915](https://github.com/grafana/oncall/issues/2915)

## 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 2023-10-19 12:53:12 +03:00 committed by GitHub
parent 35620028cc
commit 2d656c50db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 208 additions and 76 deletions

View file

@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Use shift data from event object
### Fixed
- Update ical schedule creation/update to trigger final schedule refresh ([#3156](https://github.com/grafana/oncall/pull/3156))
- Polish "Build 'When I am on-call' for web UI" [#2915](https://github.com/grafana/oncall/issues/2915)
- Fix iCal schedule incorrect view [#2001](https://github.com/grafana/oncall-private/issues/2001)
- Fix rotation name rendering issue [#2324](https://github.com/grafana/oncall/issues/2324)
### Changed

View file

@ -8,7 +8,7 @@ import hash from 'object-hash';
import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types';
import Text from 'components/Text/Text';
import ScheduleSlot from 'containers/ScheduleSlot/ScheduleSlot';
import { Event, RotationFormLiveParams, Shift, ShiftSwap } from 'models/schedule/schedule.types';
import { Event, RotationFormLiveParams, ShiftSwap } from 'models/schedule/schedule.types';
import { Timezone } from 'models/timezone/timezone.types';
import RotationTutorial from './RotationTutorial';
@ -34,7 +34,7 @@ interface RotationProps {
tutorialParams?: RotationFormLiveParams;
simplified?: boolean;
filters?: ScheduleFiltersType;
getColor?: (shiftId: Shift['id']) => string;
getColor?: (event: Event) => string;
onSlotClick?: (event: Event) => void;
emptyText?: string;
showScheduleNameAsSlotTitle?: boolean;
@ -156,7 +156,7 @@ const Rotation: FC<RotationProps> = (props) => {
event={event}
startMoment={startMoment}
currentTimezone={currentTimezone}
color={propsColor || getColor(event.shift?.pk)}
color={propsColor || getColor(event)}
handleAddOverride={getAddOverrideClickHandler(event)}
handleAddShiftSwap={getAddShiftSwapClickHandler(event)}
handleOpenSchedule={getOpenScheduleClickHandler(event)}

View file

@ -16,7 +16,7 @@ import {
getOverridesFromStore,
getShiftsFromStore,
} from 'models/schedule/schedule.helpers';
import { Schedule, Shift, ShiftSwap, Event } from 'models/schedule/schedule.types';
import { Schedule, ShiftSwap, Event } from 'models/schedule/schedule.types';
import { Timezone } from 'models/timezone/timezone.types';
import { WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
@ -59,7 +59,7 @@ class ScheduleFinal extends Component<ScheduleFinalProps> {
const currentTimeHidden = currentTimeX < 0 || currentTimeX > 1;
const getColor = (shiftId: Shift['id']) => findColor(shiftId, layers, overrides);
const getColor = (event: Event) => findColor(event.shift?.pk, layers, overrides);
return (
<>

View file

@ -46,6 +46,7 @@ interface ScheduleOverridesProps extends WithStoreProps {
onUpdate: () => void;
onDelete: () => void;
disabled: boolean;
disableShiftSwaps: boolean;
filters: ScheduleFiltersType;
}
@ -72,6 +73,7 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
store,
shiftIdToShowRotationForm,
disabled,
disableShiftSwaps,
shiftStartToShowOverrideForm: propsShiftStartToShowOverrideForm,
shiftEndToShowOverrideForm: propsShiftEndToShowOverrideForm,
onShowShiftSwapForm,
@ -112,7 +114,7 @@ class ScheduleOverrides extends Component<ScheduleOverridesProps, ScheduleOverri
<HorizontalGroup>
<Button
variant="secondary"
disabled={disabled}
disabled={disableShiftSwaps}
onClick={() => {
const closestEvent = findClosestUserEvent(dayjs(), currentUserPk, layers);
const swapStart = closestEvent

View file

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Badge, HorizontalGroup } from '@grafana/ui';
import { Badge, Button, HorizontalGroup, Icon } from '@grafana/ui';
import cn from 'classnames/bind';
import dayjs from 'dayjs';
import { observer } from 'mobx-react';
@ -12,9 +12,10 @@ import Text from 'components/Text/Text';
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
import Rotation from 'containers/Rotation/Rotation';
import { getColorForSchedule, getPersonalShiftsFromStore } from 'models/schedule/schedule.helpers';
import { Shift, Event } from 'models/schedule/schedule.types';
import { Event } from 'models/schedule/schedule.types';
import { Timezone } from 'models/timezone/timezone.types';
import { User } from 'models/user/user.types';
import { getStartOfWeek } from 'pages/schedule/Schedule.helpers';
import { WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { PLUGIN_ROOT } from 'utils/consts';
@ -32,24 +33,69 @@ interface SchedulePersonalProps extends WithStoreProps, RouteComponentProps {
onSlotClick?: (event: Event) => void;
}
@observer
class SchedulePersonal extends Component<SchedulePersonalProps> {
componentDidMount() {
const { store, startMoment } = this.props;
interface SchedulePersonalState {
startMoment?: dayjs.Dayjs;
}
store.scheduleStore.updatePersonalEvents(store.userStore.currentUserPk, startMoment);
@observer
class SchedulePersonal extends Component<SchedulePersonalProps, SchedulePersonalState> {
state: SchedulePersonalState = {};
constructor(props) {
super(props);
this.state = {
startMoment: props.startMoment,
};
}
componentDidUpdate(prevProps: Readonly<SchedulePersonalProps>): void {
const { store, startMoment } = this.props;
componentDidMount() {
const { store } = this.props;
const { startMoment } = this.state;
if (prevProps.startMoment !== this.props.startMoment) {
store.scheduleStore.updatePersonalEvents(store.userStore.currentUserPk, startMoment, 9, true);
}
componentDidUpdate(prevProps: Readonly<SchedulePersonalProps>, prevState: Readonly<SchedulePersonalState>): void {
const { store } = this.props;
const { startMoment } = this.state;
if (prevProps.currentTimezone !== this.props.currentTimezone) {
const oldTimezone = prevProps.currentTimezone;
this.setState((oldState) => {
const wDiff = oldState.startMoment.diff(getStartOfWeek(oldTimezone), 'weeks');
return { ...oldState, startMoment: getStartOfWeek(this.props.currentTimezone).add(wDiff, 'weeks') };
});
}
if (prevState.startMoment !== startMoment) {
store.scheduleStore.updatePersonalEvents(store.userStore.currentUserPk, startMoment);
}
}
handleTodayClick = () => {
const { store } = this.props;
this.setState({ startMoment: getStartOfWeek(store.currentTimezone) });
};
handleLeftClick = () => {
const { startMoment } = this.state;
this.setState({ startMoment: startMoment.add(-7, 'day') });
};
handleRightClick = () => {
const { startMoment } = this.state;
this.setState({ startMoment: startMoment.add(7, 'day') });
};
render() {
const { userPk, startMoment, currentTimezone, store, onSlotClick } = this.props;
const { userPk, currentTimezone, store, onSlotClick } = this.props;
const { startMoment } = this.state;
const base = 7 * 24 * 60; // in minutes
const diff = dayjs().tz(currentTimezone).diff(startMoment, 'minutes');
@ -60,18 +106,7 @@ class SchedulePersonal extends Component<SchedulePersonalProps> {
const currentTimeHidden = currentTimeX < 0 || currentTimeX > 1;
const getColor = (shiftId: Shift['id']) => {
const shift = store.scheduleStore.shifts[shiftId];
if (!shift) {
if (shiftId) {
store.scheduleStore.updateOncallShift(shiftId);
}
return;
}
return getColorForSchedule(shift.schedule);
};
const getColor = (event: Event) => getColorForSchedule(event.schedule?.id);
const isOncall = store.scheduleStore.onCallNow[userPk];
@ -82,12 +117,37 @@ class SchedulePersonal extends Component<SchedulePersonalProps> {
<div className={cx('root')}>
<div className={cx('header')}>
<div className={cx('title')}>
<HorizontalGroup>
<Text type="secondary">
On-call schedule <Avatar src={storeUser.avatar} size="small" /> {store.userStore.currentUser.name}
</Text>
{/* @ts-ignore */}
{isOncall ? <Badge text="On-call now" color="green" /> : <Badge text="Not on-call now" color="gray" />}
<HorizontalGroup justify="space-between">
<HorizontalGroup>
<Text type="secondary">
On-call schedule <Avatar src={storeUser.avatar} size="small" /> {storeUser.username}
</Text>
{isOncall ? (
<Badge text="On-call now" color="green" />
) : (
/* @ts-ignore */
<Badge text="Not on-call now" color="gray" />
)}
</HorizontalGroup>
<HorizontalGroup>
<HorizontalGroup>
<Text type="secondary">
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
</Text>
<Button variant="secondary" size="sm" onClick={this.handleTodayClick}>
Today
</Button>
<HorizontalGroup spacing="xs">
<Button variant="secondary" size="sm" onClick={this.handleLeftClick}>
<Icon name="angle-left" />
</Button>
<Button variant="secondary" size="sm" onClick={this.handleRightClick}>
<Icon name="angle-right" />
</Button>
</HorizontalGroup>
</HorizontalGroup>
</HorizontalGroup>
</HorizontalGroup>
</div>
</div>

View file

@ -57,7 +57,7 @@ const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {
const base = 60 * 60 * 24 * 7;
const width = duration / base;
const width = Math.max(duration / base, 0);
const currentMoment = useMemo(() => dayjs(), []);
@ -172,6 +172,7 @@ const ShiftSwapEvent = (props: ShiftSwapEventProps) => {
content={
<ScheduleSlotDetails
isShiftSwap
title="Shift swap"
beneficiaryName={beneficiary?.display_name}
user={benefactorStoreUser || beneficiaryStoreUser}
benefactorName={benefactor?.display_name}
@ -237,13 +238,17 @@ const RegularEvent = (props: RegularEventProps) => {
{users.map(({ display_name, pk: userPk, swap_request }) => {
const storeUser = store.userStore.items[userPk];
const { schedule, shift } = event;
const isCurrentUserSlot = userPk === store.userStore.currentUserPk;
const inactive = filters && filters.users.length && !filters.users.includes(userPk);
const userTitle = storeUser ? getTitle(storeUser) : display_name;
const userTitle = showScheduleNameAsSlotTitle ? schedule?.name : storeUser ? getTitle(storeUser) : display_name;
const isShiftSwap = Boolean(swap_request);
const title = isShiftSwap ? 'Shift swap' : showScheduleNameAsSlotTitle ? schedule?.name : getShiftName(shift);
let backgroundColor = color;
if (isShiftSwap) {
backgroundColor = SHIFT_SWAP_COLOR;
@ -282,7 +287,7 @@ const RegularEvent = (props: RegularEventProps) => {
key={userPk}
content={
<ScheduleSlotDetails
showScheduleNameAsSlotTitle={showScheduleNameAsSlotTitle}
title={title}
isShiftSwap={isShiftSwap}
beneficiaryName={
isShiftSwap ? (swap_request.user ? swap_request.user.display_name : display_name) : undefined
@ -328,7 +333,7 @@ interface ScheduleSlotDetailsProps {
beneficiaryName?: string;
benefactorName?: string;
currentMoment: dayjs.Dayjs;
showScheduleNameAsSlotTitle?: boolean;
title: string;
}
const ScheduleSlotDetails = (props: ScheduleSlotDetailsProps) => {
@ -344,7 +349,7 @@ const ScheduleSlotDetails = (props: ScheduleSlotDetailsProps) => {
beneficiaryName,
benefactorName,
currentMoment,
showScheduleNameAsSlotTitle,
title,
} = props;
const { scheduleStore } = useStore();
@ -368,8 +373,6 @@ const ScheduleSlotDetails = (props: ScheduleSlotDetailsProps) => {
}
}, [shift]);
const title = isShiftSwap ? 'Shift swap' : showScheduleNameAsSlotTitle ? schedule?.name : getShiftName(shift);
// const onCallNow = schedule?.on_call_now;
// const isOncall = Boolean(storeUser && onCallNow && onCallNow.some((onCallUser) => storeUser.pk === onCallUser.pk));

View file

@ -63,7 +63,7 @@ export const fillGaps = (events: Event[]) => {
return newEvents;
};
export const splitToShiftsAndFillGaps = (events: Event[]) => {
export const splitToShifts = (events: Event[]) => {
const shifts: Array<{ shiftId: Shift['id']; priority: Shift['priority_level']; events: Event[] }> = [];
for (const [_i, event] of events.entries()) {
@ -77,13 +77,20 @@ export const splitToShiftsAndFillGaps = (events: Event[]) => {
}
}
shifts.forEach((shift) => {
shift.events = fillGaps(shift.events);
});
return shifts;
};
export const fillGapsInShifts = (shifts: ShiftEvents[]) => {
return shifts.map((shift) => ({
...shift,
events: fillGaps(shift.events),
}));
};
export const enrichEventsWithScheduleData = (events: Event[], schedule: Partial<Schedule>) => {
return events.map((event) => ({ ...event, schedule }));
};
export const getPersonalShiftsFromStore = (
store: RootStore,
userPk: User['pk'],
@ -102,6 +109,44 @@ export const getShiftsFromStore = (
: (store.scheduleStore.events[scheduleId]?.['final']?.[getFromString(startMoment)] as any);
};
export const unFlattenShiftEvents = (shifts: ShiftEvents[]) => {
for (let i = 0; i < shifts.length; i++) {
const shift = shifts[i];
for (let j = 0; j < shift.events.length - 1; j++) {
for (let k = j + 1; k < shift.events.length; k++) {
const event1 = shift.events[j];
const event2 = shift.events[k];
const event1Start = dayjs(event1.start);
const event1End = dayjs(event1.end);
const event2Start = dayjs(event2.start);
const event2End = dayjs(event2.end);
if (
(event1Start.isBefore(event2Start) && event1End.isAfter(event2Start)) ||
(event1End.isAfter(event2End) && event1Start.isBefore(event2End))
) {
const firstEvent = event1Start.isBefore(event2Start) ? event1 : event2;
const secondEvent = firstEvent === event1 ? event2 : event1;
const oldShift = { ...shift, events: shift.events.filter((event) => event !== secondEvent) };
const newShift = { ...shift, events: [secondEvent] };
shifts[i] = oldShift;
shifts.push(newShift);
return unFlattenShiftEvents(shifts);
}
}
}
}
return shifts;
};
export const flattenShiftEvents = (shifts: ShiftEvents[]) => {
if (!shifts) {
return undefined;
@ -241,9 +286,7 @@ export const getOverridesFromStore = (
: (store.scheduleStore.events[scheduleId]?.['override']?.[getFromString(startMoment)] as ShiftEvents[]);
};
export const splitToLayers = (
shifts: Array<{ shiftId: Shift['id']; priority: Shift['priority_level']; events: Event[] }>
) => {
export const splitToLayers = (shifts: ShiftEvents[]) => {
return shifts
.reduce((memo, shift) => {
let layer = memo.find((level) => level.priority === shift.priority);
@ -395,7 +438,7 @@ export const getOverrideColor = (rotationIndex: number) => {
return OVERRIDE_COLORS[normalizedRotationIndex];
};
export const getShiftName = (shift: Shift) => {
export const getShiftName = (shift: Partial<Shift>) => {
if (!shift) {
return '';
}
@ -408,5 +451,5 @@ export const getShiftName = (shift: Shift) => {
return 'Override';
}
return `[L${shift.priority_level}] Rotation`;
return 'Rotation';
};

View file

@ -11,12 +11,15 @@ import { SelectOption } from 'state/types';
import {
createShiftSwapEventFromShiftSwap,
enrichEventsWithScheduleData,
enrichLayers,
enrichOverrides,
fillGapsInShifts,
flattenShiftEvents,
getFromString,
splitToLayers,
splitToShiftsAndFillGaps,
splitToShifts,
unFlattenShiftEvents,
} from './schedule.helpers';
import {
Rotation,
@ -34,7 +37,7 @@ import {
export class ScheduleStore extends BaseStore {
@observable
searchResult: { count?: number; results?: Array<Schedule['id']> } = {};
searchResult: { page_size?: number; count?: number; results?: Array<Schedule['id']> } = {};
@observable.shallow
items: { [id: string]: Schedule } = {};
@ -137,7 +140,7 @@ export class ScheduleStore extends BaseStore {
shouldUpdateFn: () => boolean = undefined
) {
const filters = typeof f === 'string' ? { search: f } : f;
const { count, results } = await makeRequest(this.path, {
const { page_size, count, results } = await makeRequest(this.path, {
method: 'GET',
params: { ...filters, page },
});
@ -157,6 +160,7 @@ export class ScheduleStore extends BaseStore {
),
};
this.searchResult = {
page_size,
count,
results: results.map((item: Schedule) => item.id),
};
@ -193,6 +197,7 @@ export class ScheduleStore extends BaseStore {
return undefined;
}
return {
page_size: this.searchResult.page_size,
count: this.searchResult.count,
results: this.searchResult.results?.map((scheduleId: Schedule['id']) => this.items[scheduleId]),
};
@ -287,7 +292,7 @@ export class ScheduleStore extends BaseStore {
this.rotationPreview = { ...this.rotationPreview, [fromString]: layers };
}
this.finalPreview = { ...this.finalPreview, [fromString]: splitToShiftsAndFillGaps(response.final) };
this.finalPreview = { ...this.finalPreview, [fromString]: fillGapsInShifts(splitToShifts(response.final)) };
}
@action
@ -450,7 +455,9 @@ export class ScheduleStore extends BaseStore {
});
const fromString = getFromString(startMoment);
const shifts = splitToShiftsAndFillGaps(response.events);
const shiftsRaw = splitToShifts(response.events);
const shiftsUnflattened = unFlattenShiftEvents(shiftsRaw);
const shifts = fillGapsInShifts(shiftsUnflattened);
const layers = type === 'rotation' ? splitToLayers(shifts) : undefined;
this.events = {
@ -535,7 +542,7 @@ export class ScheduleStore extends BaseStore {
};
}
async updatePersonalEvents(userPk: User['pk'], startMoment: dayjs.Dayjs, days = 9) {
async updatePersonalEvents(userPk: User['pk'], startMoment: dayjs.Dayjs, days = 9, isUpdateOnCallNow = false) {
const fromString = getFromString(startMoment);
const dayBefore = startMoment.subtract(1, 'day');
@ -548,8 +555,8 @@ export class ScheduleStore extends BaseStore {
},
});
const shiftEventsList = schedules.reduce((acc, schedule) => {
return [...acc, ...splitToShiftsAndFillGaps(schedule.events)];
const shiftEventsList = schedules.reduce((acc, { events, id, name }) => {
return [...acc, ...fillGapsInShifts(splitToShifts(enrichEventsWithScheduleData(events, { id, name })))];
}, []);
const shiftEventsListFlattened = flattenShiftEvents(shiftEventsList);
@ -562,9 +569,12 @@ export class ScheduleStore extends BaseStore {
},
};
this.onCallNow = {
...this.onCallNow,
[userPk]: is_oncall,
};
if (isUpdateOnCallNow) {
// since current endpoint works incorrectly we are waiting for https://github.com/grafana/oncall/issues/3164
this.onCallNow = {
...this.onCallNow,
[userPk]: is_oncall,
};
}
}
}

View file

@ -94,7 +94,7 @@ export interface Event {
is_gap: boolean;
missing_users: Array<{ display_name: User['username']; pk: User['pk'] }>;
priority_level: number;
shift: { pk: Shift['id'] | null };
shift: Pick<Shift, 'name' | 'type'> & { pk: string };
source: string;
start: string;
users: Array<{
@ -104,6 +104,7 @@ export interface Event {
}>;
is_override: boolean;
schedule?: Partial<Schedule>; // populated by frontend for personal schedule to display schedule name instead of user name
shiftSwapId?: ShiftSwap['id']; // if event is acually shift swap request (filled out by frontend)
}

View file

@ -156,6 +156,12 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
shiftIdToShowRotationForm ||
shiftSwapIdToShowForm;
const disabledShiftSwaps =
!isUserActionAllowed(UserActions.SchedulesWrite) ||
!!shiftIdToShowOverridesForm ||
shiftIdToShowRotationForm ||
shiftSwapIdToShowForm;
return (
<PageErrorHandlingWrapper errorData={errorData} objectName="schedule" pageName="schedules">
{() => (
@ -314,6 +320,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
onShowRotationForm={this.handleShowOverridesForm}
disabled={disabledOverrideForm}
disableShiftSwaps={disabledShiftSwaps}
shiftStartToShowOverrideForm={shiftStartToShowOverrideForm}
shiftEndToShowOverrideForm={shiftEndToShowOverrideForm}
onShowShiftSwapForm={!shiftSwapIdToShowForm ? this.handleShowShiftSwapForm : undefined}

View file

@ -25,7 +25,7 @@ import SchedulePersonal from 'containers/Rotations/SchedulePersonal';
import ScheduleForm from 'containers/ScheduleForm/ScheduleForm';
import TeamName from 'containers/TeamName/TeamName';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { Schedule, ScheduleType } from 'models/schedule/schedule.types';
import { Schedule } from 'models/schedule/schedule.types';
import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers';
import { Timezone } from 'models/timezone/timezone.types';
import { getStartOfWeek } from 'pages/schedule/Schedule.helpers';
@ -39,7 +39,7 @@ import styles from './Schedules.module.css';
const cx = cn.bind(styles);
const FILTERS_DEBOUNCE_MS = 500;
const ITEMS_PER_PAGE = 10;
const PAGE_SIZE_DEFAULT = 15;
interface SchedulesPageProps extends WithStoreProps, RouteComponentProps, PageProps {}
@ -83,7 +83,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
const { grafanaTeamStore } = store;
const { showNewScheduleSelector, expandedRowKeys, scheduleIdToEdit, page, startMoment } = this.state;
const { results, count } = store.scheduleStore.getSearchResult();
const { results, count, page_size } = store.scheduleStore.getSearchResult();
const columns = [
{
@ -162,9 +162,6 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
userPk={store.userStore.currentUserPk}
currentTimezone={store.currentTimezone}
startMoment={startMoment}
onSlotClick={(...rest) => {
console.log(rest);
}}
/>
</div>
<div className={cx('schedules__filters-container')}>
@ -179,7 +176,11 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
columns={columns}
data={results}
loading={!results}
pagination={{ page, total: Math.ceil((count || 0) / ITEMS_PER_PAGE), onChange: this.handlePageChange }}
pagination={{
page,
total: Math.ceil((count || 0) / (page_size || PAGE_SIZE_DEFAULT)),
onChange: this.handlePageChange,
}}
rowKey="id"
expandable={{
expandedRowKeys: expandedRowKeys,
@ -234,9 +235,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
handleCreateSchedule = (data: Schedule) => {
const { history, query } = this.props;
if (data.type === ScheduleType.API) {
history.push(`${PLUGIN_ROOT}/schedules/${data.id}?${qs.stringify(query)}`);
}
history.push(`${PLUGIN_ROOT}/schedules/${data.id}?${qs.stringify(query)}`);
};
handleExpandRow = (expanded: boolean, data: Schedule) => {
@ -455,7 +454,7 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
const { store } = this.props;
const { page, startMoment } = this.state;
store.scheduleStore.updatePersonalEvents(store.userStore.currentUserPk, startMoment);
store.scheduleStore.updatePersonalEvents(store.userStore.currentUserPk, startMoment, 9, true);
// For removal we need to check if count is 1
// which means we should change the page to the previous one