# What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Tests updated - [ ] Documentation added - [x] `CHANGELOG.md` updated
This commit is contained in:
parent
c0873007b0
commit
bee9943706
13 changed files with 336 additions and 269 deletions
|
|
@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Fixed UI permission related bug where Editors could not export their user iCal link
|
- Fixed UI permission related bug where Editors could not export their user iCal link
|
||||||
- Fixed error when a shift is created using Etc/UTC as timezone
|
- Fixed error when a shift is created using Etc/UTC as timezone
|
||||||
- Fixed issue with refresh ical file task not considering empty string values
|
- Fixed issue with refresh ical file task not considering empty string values
|
||||||
|
- Schedules: Long popup does not fit screen & buttons unreachable & objects outside of the popup ([1002](https://github.com/grafana/oncall/issues/1002))
|
||||||
|
- Can't scroll on integration settings page ([415](https://github.com/grafana/oncall/issues/415))
|
||||||
|
- Team change in the Integration page always causes 403 ([1292](https://github.com/grafana/oncall/issues/1292))
|
||||||
|
- Schedules: Permalink doesn't work with multi-teams ([940](https://github.com/grafana/oncall/issues/940))
|
||||||
|
- Schedules list -> expanded schedule blows page width ([1293](https://github.com/grafana/oncall/issues/1293))
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
top: 10%;
|
top: 10%;
|
||||||
max-height: 80%;
|
max-height: 90%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-image: initial;
|
border-image: initial;
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
box-shadow: var(--shadows-z3);
|
box-shadow: var(--shadows-z3);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1px;
|
gap: 1px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
|
|
@ -55,7 +57,6 @@
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-buttons {
|
.user-buttons {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
background: var(--background-primary);
|
||||||
|
top: -15px;
|
||||||
|
position: sticky;
|
||||||
|
margin: -15px -15px 0 -15px;
|
||||||
|
padding: 15px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -277,151 +277,155 @@ const RotationForm: FC<RotationFormProps> = observer((props) => {
|
||||||
</Draggable>
|
</Draggable>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<VerticalGroup>
|
<>
|
||||||
<HorizontalGroup justify="space-between">
|
<div className={cx('title')}>
|
||||||
<Text size="medium">
|
<HorizontalGroup justify="space-between">
|
||||||
<HorizontalGroup spacing="sm">
|
<Text size="medium">
|
||||||
<span>[L{shiftId === 'new' ? layerPriority : shift?.priority_level}]</span>
|
<HorizontalGroup spacing="sm">
|
||||||
{shiftId === 'new' ? 'New Rotation' : 'Update Rotation'}
|
<span>[L{shiftId === 'new' ? layerPriority : shift?.priority_level}]</span>
|
||||||
</HorizontalGroup>
|
{shiftId === 'new' ? 'New Rotation' : 'Update Rotation'}
|
||||||
</Text>
|
</HorizontalGroup>
|
||||||
<HorizontalGroup>
|
</Text>
|
||||||
{shiftId !== 'new' && (
|
|
||||||
<IconButton
|
|
||||||
variant="secondary"
|
|
||||||
tooltip="Delete"
|
|
||||||
name="trash-alt"
|
|
||||||
onClick={() => setShowDeleteRotationConfirmation(true)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<IconButton variant="secondary" className={cx('drag-handler')} name="draggabledots" />
|
|
||||||
</HorizontalGroup>
|
|
||||||
</HorizontalGroup>
|
|
||||||
<div className={cx('content')}>
|
|
||||||
<VerticalGroup>
|
|
||||||
<div className={cx('two-fields')}>
|
|
||||||
<Field
|
|
||||||
label={
|
|
||||||
<Text type="primary" size="small">
|
|
||||||
Rotation start
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DateTimePicker
|
|
||||||
minMoment={shiftStart}
|
|
||||||
value={rotationStart}
|
|
||||||
onChange={setRotationStart}
|
|
||||||
timezone={currentTimezone}
|
|
||||||
onFocus={getFocusHandler('rotationStart')}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
label={
|
|
||||||
<HorizontalGroup spacing="xs">
|
|
||||||
<Text type="primary" size="small">
|
|
||||||
Rotation end
|
|
||||||
</Text>
|
|
||||||
<InlineSwitch
|
|
||||||
className={cx('inline-switch')}
|
|
||||||
transparent
|
|
||||||
value={!endLess}
|
|
||||||
onChange={handleChangeEndless}
|
|
||||||
/>
|
|
||||||
</HorizontalGroup>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{endLess ? (
|
|
||||||
<div style={{ lineHeight: '32px' }}>
|
|
||||||
<Text type="secondary">Endless</Text>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<DateTimePicker value={rotationEnd} onChange={setRotationEnd} timezone={currentTimezone} />
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<Field className={cx('control')} label="Repeat shifts every">
|
{shiftId !== 'new' && (
|
||||||
<Select
|
<IconButton
|
||||||
maxMenuHeight={120}
|
variant="secondary"
|
||||||
value={repeatEveryValue}
|
tooltip="Delete"
|
||||||
options={repeatShiftsEveryOptions}
|
name="trash-alt"
|
||||||
onChange={handleRepeatEveryValueChange}
|
onClick={() => setShowDeleteRotationConfirmation(true)}
|
||||||
allowCustomValue
|
|
||||||
/>
|
/>
|
||||||
</Field>
|
)}
|
||||||
<Field className={cx('control')} label="">
|
<IconButton variant="secondary" className={cx('drag-handler')} name="draggabledots" />
|
||||||
<RemoteSelect
|
|
||||||
href="/oncall_shifts/frequency_options/"
|
|
||||||
value={repeatEveryPeriod}
|
|
||||||
onChange={setRepeatEveryPeriod}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
{(repeatEveryPeriod === 0 || repeatEveryPeriod === 1) && (
|
|
||||||
<Field label="Select days to repeat">
|
|
||||||
<DaysSelector
|
|
||||||
options={store.scheduleStore.byDayOptions}
|
|
||||||
value={selectedDays}
|
|
||||||
onChange={(value) => setSelectedDays(value)}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
<div className={cx('two-fields')}>
|
|
||||||
<Field
|
|
||||||
className={cx('date-time-picker')}
|
|
||||||
label={
|
|
||||||
<Text type="primary" size="small">
|
|
||||||
Parent shift start
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DateTimePicker
|
|
||||||
value={shiftStart}
|
|
||||||
onChange={updateShiftStart}
|
|
||||||
timezone={currentTimezone}
|
|
||||||
onFocus={getFocusHandler('shiftStart')}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
className={cx('date-time-picker')}
|
|
||||||
label={
|
|
||||||
<Text type="primary" size="small">
|
|
||||||
Parent shift end
|
|
||||||
</Text>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DateTimePicker
|
|
||||||
value={shiftEnd}
|
|
||||||
onChange={setShiftEnd}
|
|
||||||
timezone={currentTimezone}
|
|
||||||
onFocus={getFocusHandler('shiftEnd')}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
<UserGroups
|
|
||||||
value={userGroups}
|
|
||||||
onChange={setUserGroups}
|
|
||||||
isMultipleGroups={true}
|
|
||||||
renderUser={renderUser}
|
|
||||||
showError={!isFormValid}
|
|
||||||
/>
|
|
||||||
</VerticalGroup>
|
|
||||||
</div>
|
|
||||||
<HorizontalGroup justify="space-between">
|
|
||||||
<Text type="secondary">Timezone: {getTzOffsetString(dayjs().tz(currentTimezone))}</Text>
|
|
||||||
<HorizontalGroup>
|
|
||||||
<Button variant="secondary" onClick={onHide}>
|
|
||||||
{shiftId === 'new' ? 'Cancel' : 'Close'}
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" onClick={handleCreate} disabled={!isFormValid}>
|
|
||||||
{shiftId === 'new' ? 'Create' : 'Update'}
|
|
||||||
</Button>
|
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</HorizontalGroup>
|
</div>
|
||||||
</VerticalGroup>
|
<VerticalGroup>
|
||||||
|
<div className={cx('content')}>
|
||||||
|
<VerticalGroup>
|
||||||
|
<div className={cx('two-fields')}>
|
||||||
|
<Field
|
||||||
|
label={
|
||||||
|
<Text type="primary" size="small">
|
||||||
|
Rotation start
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DateTimePicker
|
||||||
|
minMoment={shiftStart}
|
||||||
|
value={rotationStart}
|
||||||
|
onChange={setRotationStart}
|
||||||
|
timezone={currentTimezone}
|
||||||
|
onFocus={getFocusHandler('rotationStart')}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
label={
|
||||||
|
<HorizontalGroup spacing="xs">
|
||||||
|
<Text type="primary" size="small">
|
||||||
|
Rotation end
|
||||||
|
</Text>
|
||||||
|
<InlineSwitch
|
||||||
|
className={cx('inline-switch')}
|
||||||
|
transparent
|
||||||
|
value={!endLess}
|
||||||
|
onChange={handleChangeEndless}
|
||||||
|
/>
|
||||||
|
</HorizontalGroup>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{endLess ? (
|
||||||
|
<div style={{ lineHeight: '32px' }}>
|
||||||
|
<Text type="secondary">Endless</Text>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<DateTimePicker value={rotationEnd} onChange={setRotationEnd} timezone={currentTimezone} />
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Field className={cx('control')} label="Repeat shifts every">
|
||||||
|
<Select
|
||||||
|
maxMenuHeight={120}
|
||||||
|
value={repeatEveryValue}
|
||||||
|
options={repeatShiftsEveryOptions}
|
||||||
|
onChange={handleRepeatEveryValueChange}
|
||||||
|
allowCustomValue
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field className={cx('control')} label="">
|
||||||
|
<RemoteSelect
|
||||||
|
href="/oncall_shifts/frequency_options/"
|
||||||
|
value={repeatEveryPeriod}
|
||||||
|
onChange={setRepeatEveryPeriod}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</HorizontalGroup>
|
||||||
|
{(repeatEveryPeriod === 0 || repeatEveryPeriod === 1) && (
|
||||||
|
<Field label="Select days to repeat">
|
||||||
|
<DaysSelector
|
||||||
|
options={store.scheduleStore.byDayOptions}
|
||||||
|
value={selectedDays}
|
||||||
|
onChange={(value) => setSelectedDays(value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
<div className={cx('two-fields')}>
|
||||||
|
<Field
|
||||||
|
className={cx('date-time-picker')}
|
||||||
|
label={
|
||||||
|
<Text type="primary" size="small">
|
||||||
|
Parent shift start
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DateTimePicker
|
||||||
|
value={shiftStart}
|
||||||
|
onChange={updateShiftStart}
|
||||||
|
timezone={currentTimezone}
|
||||||
|
onFocus={getFocusHandler('shiftStart')}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
className={cx('date-time-picker')}
|
||||||
|
label={
|
||||||
|
<Text type="primary" size="small">
|
||||||
|
Parent shift end
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<DateTimePicker
|
||||||
|
value={shiftEnd}
|
||||||
|
onChange={setShiftEnd}
|
||||||
|
timezone={currentTimezone}
|
||||||
|
onFocus={getFocusHandler('shiftEnd')}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
<UserGroups
|
||||||
|
value={userGroups}
|
||||||
|
onChange={setUserGroups}
|
||||||
|
isMultipleGroups={true}
|
||||||
|
renderUser={renderUser}
|
||||||
|
showError={!isFormValid}
|
||||||
|
/>
|
||||||
|
</VerticalGroup>
|
||||||
|
</div>
|
||||||
|
<HorizontalGroup justify="space-between">
|
||||||
|
<Text type="secondary">Timezone: {getTzOffsetString(dayjs().tz(currentTimezone))}</Text>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Button variant="secondary" onClick={onHide}>
|
||||||
|
{shiftId === 'new' ? 'Cancel' : 'Close'}
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" onClick={handleCreate} disabled={!isFormValid}>
|
||||||
|
{shiftId === 'new' ? 'Create' : 'Update'}
|
||||||
|
</Button>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</VerticalGroup>
|
||||||
|
</>
|
||||||
{showDeleteRotationConfirmation && (
|
{showDeleteRotationConfirmation && (
|
||||||
<GrafanaModal
|
<GrafanaModal
|
||||||
isOpen
|
isOpen
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
|
@ -54,7 +56,6 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
|
|
||||||
|
|
@ -123,12 +123,14 @@ const ScheduleSlot: FC<ScheduleSlotProps> = observer((props) => {
|
||||||
duration={duration}
|
duration={duration}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{userIndex === 0 && label && (
|
<div className={cx('title')}>
|
||||||
<div className={cx('label')} style={{ color }}>
|
{userIndex === 0 && label && (
|
||||||
{label}
|
<div className={cx('label')} style={{ color }}>
|
||||||
</div>
|
{label}
|
||||||
)}
|
</div>
|
||||||
<div className={cx('title')}>{title}</div>
|
)}
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
/* Navigation/Layout */
|
/* Navigation/Layout */
|
||||||
|
|
||||||
|
.drawer-content {
|
||||||
|
overflow: auto !important; /* fix https://github.com/grafana/oncall/issues/415 */
|
||||||
|
}
|
||||||
|
|
||||||
.page-body {
|
.page-body {
|
||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,8 @@ export class ScheduleStore extends BaseStore {
|
||||||
...this.items,
|
...this.items,
|
||||||
[item.id]: item,
|
[item.id]: item,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,14 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
||||||
this.update().then(() => this.parseQueryParams(true));
|
this.update().then(() => this.parseQueryParams(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Readonly<IntegrationsProps>): void {
|
||||||
|
if (prevProps.match.params.id && !this.props.match.params.id) {
|
||||||
|
this.setState({ errorData: initErrorDataState() }, () => {
|
||||||
|
this.parseQueryParams();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedAlertReceiveChannel = (alertReceiveChannelId: AlertReceiveChannel['id'], shouldRedirect = false) => {
|
setSelectedAlertReceiveChannel = (alertReceiveChannelId: AlertReceiveChannel['id'], shouldRedirect = false) => {
|
||||||
const { store, history } = this.props;
|
const { store, history } = this.props;
|
||||||
store.selectedAlertReceiveChannel = alertReceiveChannelId;
|
store.selectedAlertReceiveChannel = alertReceiveChannelId;
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,8 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.not-found {
|
||||||
|
margin: 50px auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,11 @@ import dayjs from 'dayjs';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import PageErrorHandlingWrapper from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
|
import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
|
||||||
|
import {
|
||||||
|
getWrongTeamResponseInfo,
|
||||||
|
initErrorDataState,
|
||||||
|
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||||
import PluginLink from 'components/PluginLink/PluginLink';
|
import PluginLink from 'components/PluginLink/PluginLink';
|
||||||
import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning';
|
import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning';
|
||||||
import Text from 'components/Text/Text';
|
import Text from 'components/Text/Text';
|
||||||
|
|
@ -33,7 +37,7 @@ const cx = cn.bind(styles);
|
||||||
|
|
||||||
interface SchedulePageProps extends PageProps, WithStoreProps, RouteComponentProps<{ id: string }> {}
|
interface SchedulePageProps extends PageProps, WithStoreProps, RouteComponentProps<{ id: string }> {}
|
||||||
|
|
||||||
interface SchedulePageState {
|
interface SchedulePageState extends PageBaseState {
|
||||||
startMoment: dayjs.Dayjs;
|
startMoment: dayjs.Dayjs;
|
||||||
schedulePeriodType: string;
|
schedulePeriodType: string;
|
||||||
renderType: string;
|
renderType: string;
|
||||||
|
|
@ -59,6 +63,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
showEditForm: false,
|
showEditForm: false,
|
||||||
showScheduleICalSettings: false,
|
showScheduleICalSettings: false,
|
||||||
|
errorData: initErrorDataState(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,8 +106,11 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
||||||
shiftIdToShowOverridesForm,
|
shiftIdToShowOverridesForm,
|
||||||
showEditForm,
|
showEditForm,
|
||||||
showScheduleICalSettings,
|
showScheduleICalSettings,
|
||||||
|
errorData,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
const { isNotFoundError } = errorData;
|
||||||
|
|
||||||
const { scheduleStore, currentTimezone } = store;
|
const { scheduleStore, currentTimezone } = store;
|
||||||
|
|
||||||
const users = store.userStore.getSearchResult().results;
|
const users = store.userStore.getSearchResult().results;
|
||||||
|
|
@ -115,131 +123,147 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
||||||
shiftIdToShowOverridesForm;
|
shiftIdToShowOverridesForm;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageErrorHandlingWrapper pageName="schedules">
|
<PageErrorHandlingWrapper errorData={errorData} objectName="schedule" pageName="schedules">
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
<div className={cx('root')}>
|
<div className={cx('root')}>
|
||||||
<VerticalGroup spacing="lg">
|
{isNotFoundError ? (
|
||||||
<div className={cx('header')}>
|
<div className={cx('not-found')}>
|
||||||
<HorizontalGroup justify="space-between">
|
<VerticalGroup spacing="lg" align="center">
|
||||||
<HorizontalGroup>
|
<Text.Title level={1}>404</Text.Title>
|
||||||
<PluginLink query={{ page: 'schedules' }}>
|
<Text.Title level={4}>Schedule not found</Text.Title>
|
||||||
<IconButton style={{ marginTop: '5px' }} name="arrow-left" size="xl" />
|
<PluginLink query={{ page: 'schedules' }}>
|
||||||
</PluginLink>
|
<Button variant="secondary" icon="arrow-left" size="md">
|
||||||
<Text.Title
|
Go to Schedules page
|
||||||
editable
|
</Button>
|
||||||
editModalTitle="Schedule name"
|
</PluginLink>
|
||||||
level={2}
|
</VerticalGroup>
|
||||||
onTextChange={this.handleNameChange}
|
|
||||||
>
|
|
||||||
{schedule?.name}
|
|
||||||
</Text.Title>
|
|
||||||
{schedule && <ScheduleWarning item={schedule} />}
|
|
||||||
</HorizontalGroup>
|
|
||||||
<HorizontalGroup spacing="lg">
|
|
||||||
{users && (
|
|
||||||
<HorizontalGroup>
|
|
||||||
<Text type="secondary">Current timezone:</Text>
|
|
||||||
<UserTimezoneSelect
|
|
||||||
value={currentTimezone}
|
|
||||||
users={users}
|
|
||||||
onChange={this.handleTimezoneChange}
|
|
||||||
/>
|
|
||||||
</HorizontalGroup>
|
|
||||||
)}
|
|
||||||
<HorizontalGroup>
|
|
||||||
<HorizontalGroup>
|
|
||||||
<HorizontalGroup>
|
|
||||||
<Button variant="secondary" onClick={this.handleExportClick()}>
|
|
||||||
Export
|
|
||||||
</Button>
|
|
||||||
</HorizontalGroup>
|
|
||||||
|
|
||||||
{(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && (
|
|
||||||
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
|
|
||||||
Reload
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</HorizontalGroup>
|
|
||||||
<ToolbarButton
|
|
||||||
icon="cog"
|
|
||||||
tooltip="Settings"
|
|
||||||
onClick={() => {
|
|
||||||
this.setState({ showEditForm: true });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<WithConfirm>
|
|
||||||
<ToolbarButton icon="trash-alt" tooltip="Delete" onClick={this.handleDelete} />
|
|
||||||
</WithConfirm>
|
|
||||||
</HorizontalGroup>
|
|
||||||
</HorizontalGroup>
|
|
||||||
</HorizontalGroup>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={cx('users-timezones')}>
|
) : (
|
||||||
<UsersTimezones
|
<VerticalGroup spacing="lg">
|
||||||
scheduleId={scheduleId}
|
<div className={cx('header')}>
|
||||||
startMoment={startMoment}
|
|
||||||
onCallNow={schedule?.on_call_now || []}
|
|
||||||
userIds={
|
|
||||||
scheduleStore.relatedUsers[scheduleId] ? Object.keys(scheduleStore.relatedUsers[scheduleId]) : []
|
|
||||||
}
|
|
||||||
tz={currentTimezone}
|
|
||||||
onTzChange={this.handleTimezoneChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={cx('rotations')}>
|
|
||||||
<div className={cx('controls')}>
|
|
||||||
<HorizontalGroup justify="space-between">
|
<HorizontalGroup justify="space-between">
|
||||||
<HorizontalGroup>
|
<HorizontalGroup>
|
||||||
<Button variant="secondary" onClick={this.handleTodayClick}>
|
<PluginLink query={{ page: 'schedules' }}>
|
||||||
Today
|
<IconButton style={{ marginTop: '5px' }} name="arrow-left" size="xl" />
|
||||||
</Button>
|
</PluginLink>
|
||||||
<HorizontalGroup spacing="xs">
|
<Text.Title
|
||||||
<Button variant="secondary" onClick={this.handleLeftClick}>
|
editable
|
||||||
<Icon name="angle-left" />
|
editModalTitle="Schedule name"
|
||||||
</Button>
|
level={2}
|
||||||
<Button variant="secondary" onClick={this.handleRightClick}>
|
onTextChange={this.handleNameChange}
|
||||||
<Icon name="angle-right" />
|
>
|
||||||
</Button>
|
{schedule?.name}
|
||||||
</HorizontalGroup>
|
|
||||||
<Text.Title style={{ marginLeft: '8px' }} level={4} type="primary">
|
|
||||||
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
|
|
||||||
</Text.Title>
|
</Text.Title>
|
||||||
|
{schedule && <ScheduleWarning item={schedule} />}
|
||||||
|
</HorizontalGroup>
|
||||||
|
<HorizontalGroup spacing="lg">
|
||||||
|
{users && (
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Text type="secondary">Current timezone:</Text>
|
||||||
|
<UserTimezoneSelect
|
||||||
|
value={currentTimezone}
|
||||||
|
users={users}
|
||||||
|
onChange={this.handleTimezoneChange}
|
||||||
|
/>
|
||||||
|
</HorizontalGroup>
|
||||||
|
)}
|
||||||
|
<HorizontalGroup>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Button variant="secondary" onClick={this.handleExportClick()}>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</HorizontalGroup>
|
||||||
|
|
||||||
|
{(schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar) && (
|
||||||
|
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
|
||||||
|
Reload
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</HorizontalGroup>
|
||||||
|
<ToolbarButton
|
||||||
|
icon="cog"
|
||||||
|
tooltip="Settings"
|
||||||
|
onClick={() => {
|
||||||
|
this.setState({ showEditForm: true });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<WithConfirm>
|
||||||
|
<ToolbarButton icon="trash-alt" tooltip="Delete" onClick={this.handleDelete} />
|
||||||
|
</WithConfirm>
|
||||||
|
</HorizontalGroup>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</div>
|
</div>
|
||||||
<ScheduleFinal
|
<div className={cx('users-timezones')}>
|
||||||
scheduleId={scheduleId}
|
<UsersTimezones
|
||||||
currentTimezone={currentTimezone}
|
scheduleId={scheduleId}
|
||||||
startMoment={startMoment}
|
startMoment={startMoment}
|
||||||
onClick={this.handleShowForm}
|
onCallNow={schedule?.on_call_now || []}
|
||||||
disabled={disabled}
|
userIds={
|
||||||
/>
|
scheduleStore.relatedUsers[scheduleId]
|
||||||
<Rotations
|
? Object.keys(scheduleStore.relatedUsers[scheduleId])
|
||||||
scheduleId={scheduleId}
|
: []
|
||||||
currentTimezone={currentTimezone}
|
}
|
||||||
startMoment={startMoment}
|
tz={currentTimezone}
|
||||||
onCreate={this.handleCreateRotation}
|
onTzChange={this.handleTimezoneChange}
|
||||||
onUpdate={this.handleUpdateRotation}
|
/>
|
||||||
onDelete={this.handleDeleteRotation}
|
</div>
|
||||||
shiftIdToShowRotationForm={shiftIdToShowRotationForm}
|
|
||||||
onShowRotationForm={this.handleShowRotationForm}
|
<div className={cx('rotations')}>
|
||||||
disabled={disabled}
|
<div className={cx('controls')}>
|
||||||
/>
|
<HorizontalGroup justify="space-between">
|
||||||
<ScheduleOverrides
|
<HorizontalGroup>
|
||||||
scheduleId={scheduleId}
|
<Button variant="secondary" onClick={this.handleTodayClick}>
|
||||||
currentTimezone={currentTimezone}
|
Today
|
||||||
startMoment={startMoment}
|
</Button>
|
||||||
onCreate={this.handleCreateOverride}
|
<HorizontalGroup spacing="xs">
|
||||||
onUpdate={this.handleUpdateOverride}
|
<Button variant="secondary" onClick={this.handleLeftClick}>
|
||||||
onDelete={this.handleDeleteOverride}
|
<Icon name="angle-left" />
|
||||||
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
|
</Button>
|
||||||
onShowRotationForm={this.handleShowOverridesForm}
|
<Button variant="secondary" onClick={this.handleRightClick}>
|
||||||
disabled={disabled}
|
<Icon name="angle-right" />
|
||||||
/>
|
</Button>
|
||||||
</div>
|
</HorizontalGroup>
|
||||||
</VerticalGroup>
|
<Text.Title style={{ marginLeft: '8px' }} level={4} type="primary">
|
||||||
|
{startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')}
|
||||||
|
</Text.Title>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</div>
|
||||||
|
<ScheduleFinal
|
||||||
|
scheduleId={scheduleId}
|
||||||
|
currentTimezone={currentTimezone}
|
||||||
|
startMoment={startMoment}
|
||||||
|
onClick={this.handleShowForm}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Rotations
|
||||||
|
scheduleId={scheduleId}
|
||||||
|
currentTimezone={currentTimezone}
|
||||||
|
startMoment={startMoment}
|
||||||
|
onCreate={this.handleCreateRotation}
|
||||||
|
onUpdate={this.handleUpdateRotation}
|
||||||
|
onDelete={this.handleDeleteRotation}
|
||||||
|
shiftIdToShowRotationForm={shiftIdToShowRotationForm}
|
||||||
|
onShowRotationForm={this.handleShowRotationForm}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<ScheduleOverrides
|
||||||
|
scheduleId={scheduleId}
|
||||||
|
currentTimezone={currentTimezone}
|
||||||
|
startMoment={startMoment}
|
||||||
|
onCreate={this.handleCreateOverride}
|
||||||
|
onUpdate={this.handleUpdateOverride}
|
||||||
|
onDelete={this.handleDeleteOverride}
|
||||||
|
shiftIdToShowRotationForm={shiftIdToShowOverridesForm}
|
||||||
|
onShowRotationForm={this.handleShowOverridesForm}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</VerticalGroup>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{showEditForm && schedule && (
|
{showEditForm && schedule && (
|
||||||
<ScheduleForm
|
<ScheduleForm
|
||||||
|
|
@ -325,7 +349,9 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
||||||
|
|
||||||
const { startMoment } = this.state;
|
const { startMoment } = this.state;
|
||||||
|
|
||||||
store.scheduleStore.updateItem(scheduleId); // to refresh current oncall users
|
store.scheduleStore
|
||||||
|
.updateItem(scheduleId) // to refresh current oncall users
|
||||||
|
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
|
||||||
store.scheduleStore.updateRelatedUsers(scheduleId); // to refresh related users
|
store.scheduleStore.updateRelatedUsers(scheduleId); // to refresh related users
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
.schedule {
|
.schedule {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
max-width: calc(100vw - 104px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue