From 732d5b6076d60ac71e913589950a0bec73edc2b9 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Tue, 7 May 2024 10:27:10 +0100 Subject: [PATCH 01/28] remove schedule tutorial code --- .../containers/Rotation/Rotation.module.css | 17 --- .../src/containers/Rotation/Rotation.tsx | 7 +- .../containers/Rotation/RotationTutorial.tsx | 117 ------------------ .../src/containers/Rotations/Rotations.tsx | 1 - .../src/models/schedule/schedule.ts | 9 -- .../src/models/schedule/schedule.types.ts | 9 -- 6 files changed, 1 insertion(+), 159 deletions(-) delete mode 100644 grafana-plugin/src/containers/Rotation/RotationTutorial.tsx diff --git a/grafana-plugin/src/containers/Rotation/Rotation.module.css b/grafana-plugin/src/containers/Rotation/Rotation.module.css index 87cb524c0d..ca7a578a0b 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.module.css +++ b/grafana-plugin/src/containers/Rotation/Rotation.module.css @@ -64,11 +64,6 @@ margin: 0 2px; } -.slots--tutorial { - position: absolute; - background: rgba(61, 113, 217, 0.15); -} - .pointer { position: absolute; top: -9px; @@ -82,15 +77,3 @@ transform: scale(1); opacity: 1; } - -.tutorial-slot { - width: 175px; - height: 28px; - border-radius: 2px; - margin: 0 1px; - padding: 4px; -} - -.tutorial-slot--active { - box-shadow: var(--shadows-z3); -} diff --git a/grafana-plugin/src/containers/Rotation/Rotation.tsx b/grafana-plugin/src/containers/Rotation/Rotation.tsx index f75e738a06..380319598b 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.tsx +++ b/grafana-plugin/src/containers/Rotation/Rotation.tsx @@ -9,11 +9,9 @@ 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, ShiftSwap } from 'models/schedule/schedule.types'; +import { Event, ShiftSwap } from 'models/schedule/schedule.types'; import { useStore } from 'state/useStore'; -import { RotationTutorial } from './RotationTutorial'; - import styles from './Rotation.module.css'; const cx = cn.bind(styles); @@ -30,7 +28,6 @@ interface RotationProps { onShiftSwapClick?: (swapId: ShiftSwap['id']) => void; days?: number; transparent?: boolean; - tutorialParams?: RotationFormLiveParams; simplified?: boolean; filters?: ScheduleFiltersType; getColor?: (event: Event) => string; @@ -48,7 +45,6 @@ export const Rotation: FC = observer((props) => { color: propsColor, days = 7, transparent = false, - tutorialParams, onClick, handleAddOverride, handleAddShiftSwap, @@ -145,7 +141,6 @@ export const Rotation: FC = observer((props) => { return (
- {tutorialParams && } {events ? ( events.length ? (
= observer((props) => { - const { - timezoneStore: { calendarStartDate }, - } = useStore(); - const { days = 7, shiftStart, shiftEnd, rotationStart, focusElementName } = props; - - const duration = shiftEnd.diff(shiftStart, 'seconds'); - - const events = useMemo(() => { - return [ - { - start: dayjs(shiftStart), - end: dayjs(shiftStart).add(duration, 'seconds'), - }, - ]; - }, [shiftStart, duration]); - - const base = 60 * 60 * 24 * days; - - const pointerX = useMemo(() => { - if (focusElementName === undefined) { - return undefined; - } - - const moment = props[focusElementName]; - const firstEvent = events[0]; - const diff = dayjs(moment).diff(firstEvent.start, 'seconds'); - - return diff / base; - }, [focusElementName, events, rotationStart]); - - const x = useMemo(() => { - if (!events || !events.length) { - return 0; - } - - const firstEvent = events[0]; - const firstShiftOffset = dayjs(firstEvent.start).diff(calendarStartDate, 'seconds'); - const base = 60 * 60 * 24 * days; - - return firstShiftOffset / base; - }, [events, calendarStartDate]); - - return ( -
- - {events.map((event, index) => { - const duration = event.end.diff(event.start, 'seconds'); - const width = duration / base; - return ( - - ); - })} -
- ); -}); - -const TutorialSlot = (props: { style: React.CSSProperties; active: boolean }) => { - const { style, active } = props; - - return
; -}; - -const Pointer = (props: { className: string; style: React.CSSProperties }) => { - const { className, style } = props; - - return ( - - - - - - - - - - ); -}; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index bb91f081d0..2e26cb3e21 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -180,7 +180,6 @@ class _Rotations extends Component { layerIndex={layerIndex} rotationIndex={rotationIndex} transparent={isPreview} - tutorialParams={isPreview && store.scheduleStore.rotationFormLiveParams} filters={filters} onSlotClick={onSlotClick} /> diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index ded9afaa47..f455ad99f6 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -34,7 +34,6 @@ import { Event, Layer, ShiftEvents, - RotationFormLiveParams, ScheduleScoreQualityResponse, ShiftSwap, } from './schedule.types'; @@ -108,9 +107,6 @@ export class ScheduleStore extends BaseStore { @observable overridePreview?: { [fromString: string]: ShiftEvents[] }; - @observable - rotationFormLiveParams: RotationFormLiveParams = undefined; - @observable scheduleToScheduleEvents: { [id: string]: ScheduleEvent[]; @@ -277,10 +273,6 @@ export class ScheduleStore extends BaseStore { return response; } - setRotationFormLiveParams(params: RotationFormLiveParams) { - this.rotationFormLiveParams = params; - } - async updateRotationPreview( scheduleId: Schedule['id'], shiftId: Shift['id'] | 'new', @@ -356,7 +348,6 @@ export class ScheduleStore extends BaseStore { this.rotationPreview = undefined; this.overridePreview = undefined; this.shiftSwapsPreview = undefined; - this.rotationFormLiveParams = undefined; } @action.bound diff --git a/grafana-plugin/src/models/schedule/schedule.types.ts b/grafana-plugin/src/models/schedule/schedule.types.ts index 902d7c1ed7..1a7e8eb11d 100644 --- a/grafana-plugin/src/models/schedule/schedule.types.ts +++ b/grafana-plugin/src/models/schedule/schedule.types.ts @@ -1,5 +1,3 @@ -import dayjs from 'dayjs'; - import { GrafanaTeam } from 'models/grafana_team/grafana_team.types'; import { SlackChannel } from 'models/slack_channel/slack_channel.types'; import { UserGroup } from 'models/user_group/user_group.types'; @@ -11,13 +9,6 @@ export enum ScheduleType { 'API', } -export interface RotationFormLiveParams { - rotationStart: dayjs.Dayjs; - shiftStart: dayjs.Dayjs; - shiftEnd: dayjs.Dayjs; - focusElementName: string; -} - export interface Schedule { id: string; ical_url_primary: string; From 03945591ac6489bf87f5e0e48fc59dadb7d493c4 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Tue, 7 May 2024 13:53:12 +0100 Subject: [PATCH 02/28] update layers visualization --- .../containers/RotationForm/RotationForm.tsx | 2 +- .../containers/Rotations/Rotations.module.css | 24 +++++++++++-------- .../src/containers/Rotations/Rotations.tsx | 21 ++++++++-------- .../containers/Rotations/ScheduleFinal.tsx | 2 +- .../Rotations/ScheduleOverrides.tsx | 11 +++++++-- .../TimelineMarks/TimelineMarks.tsx | 8 +++---- .../src/pages/schedule/Schedule.module.css | 7 ++++++ 7 files changed, 45 insertions(+), 30 deletions(-) diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index e45fc18698..c9f9b9ae5b 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -766,7 +766,7 @@ export const RotationForm = observer((props: RotationFormProps) => { ); function onResize() { - onHide(); + //onHide(); } function onDraggableInit(_e: DraggableEvent, data: DraggableData) { diff --git a/grafana-plugin/src/containers/Rotations/Rotations.module.css b/grafana-plugin/src/containers/Rotations/Rotations.module.css index b764648cd3..7bb3634220 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.module.css +++ b/grafana-plugin/src/containers/Rotations/Rotations.module.css @@ -24,8 +24,17 @@ margin: 16px 0; } +.layers { + position: relative; +} + .layer { display: block; + margin: 10px 0; +} + +.layer-first { + margin-top: 30px; } .rotations { @@ -33,15 +42,12 @@ } .layer-title { - text-align: center; - font-weight: 500; - line-height: 16px; - padding: 8px; - background: var(--secondary-background); + margin-left: 10px; + margin-bottom: 10px; } -.layer-title:hover { - background: rgba(204, 204, 220, 0.12); +.layer-title_first { + margin-top: 40px; } .rotations-plus-title { @@ -51,8 +57,6 @@ .header-plus-content { position: relative; - padding-top: 26px; - padding-bottom: 26px; } .layer-header { @@ -76,7 +80,7 @@ font-weight: 400; font-size: 12px; line-height: 16px; - text-align: center; + text-align: left; padding: 12px; cursor: pointer; } diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 2e26cb3e21..756977c3db 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -8,6 +8,7 @@ import { observer } from 'mobx-react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types'; +import { Tag } from 'components/Tag/Tag'; import { Text } from 'components/Text/Text'; import { Rotation } from 'containers/Rotation/Rotation'; import { RotationForm } from 'containers/RotationForm/RotationForm'; @@ -148,19 +149,17 @@ class _Rotations extends Component {
{layers && layers.length ? ( + + {!currentTimeHidden && ( +
+ )} {layers.map((layer, layerIndex) => ( -
-
- - Layer {layer.priority} - -
+
+ + Layer {layer.priority} +
- - {!currentTimeHidden && ( -
- )} {layer.shifts.map(({ shiftId, isPreview, events }, rotationIndex) => ( { this.handleAddLayer(nextPriority, store.timezoneStore.calendarStartDate); }} > - + Add new layer with rotation + + Add new layer with rotation
)}
diff --git a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx index 51837b6f8d..9f436c7f25 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx @@ -82,7 +82,7 @@ const _ScheduleFinal: FC = observer(
{!currentTimeHidden &&
} - + {shifts && shifts.length ? ( shifts.map(({ events }, index) => { return ( diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index f4e81887af..a8726d59b2 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -7,6 +7,7 @@ import { observer } from 'mobx-react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types'; +import { Tag } from 'components/Tag/Tag'; import { Text } from 'components/Text/Text'; import { Rotation } from 'containers/Rotation/Rotation'; import { ScheduleOverrideForm } from 'containers/RotationForm/ScheduleOverrideForm'; @@ -150,7 +151,10 @@ class _ScheduleOverrides extends Component {!currentTimeHidden &&
} - + + + Swaps + {shiftSwaps && shiftSwaps.length ? shiftSwaps.map(({ isPreview, events }, index) => ( @@ -170,7 +174,10 @@ class _ScheduleOverrides extends Component - + + + Overrides + {shifts && shifts.length ? ( shifts.map(({ shiftId, isPreview, events }, index) => ( diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx index 95d23fce44..d8e84eb2c1 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx @@ -73,11 +73,9 @@ export const TimelineMarks: FC = observer((props) => { return (
- - {m.moment.format('ddd D MMM')} - + {m.moment.format('ddd D MMM')}
-
+ {/*
{m.moments.map((mm, j) => (
= observer((props) => {
))} -
+
*/}
); })} diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css index 351819f45f..e6dc9f8bc5 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ b/grafana-plugin/src/pages/schedule/Schedule.module.css @@ -28,6 +28,13 @@ .controls { width: 100%; + position: sticky; + top: 10px; + z-index: 2; + padding: 10px; + background: var(--background-secondary); + border: var(--border-weak); + border-radius: var(--border-radius); } .rotations { From ddeef32d043456a93a6a7ec632d4b2a7149c408b Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Wed, 8 May 2024 15:50:11 +0100 Subject: [PATCH 03/28] add superbutton to the schedule page --- .../containers/RotationForm/RotationForm.tsx | 6 +- .../RotationForm/ScheduleOverrideForm.tsx | 4 +- .../containers/Rotations/Rotations.module.css | 4 - .../src/containers/Rotations/Rotations.tsx | 49 ++++---- .../Rotations/ScheduleOverrides.tsx | 71 ++++------- .../src/pages/schedule/Schedule.tsx | 118 ++++++++++++++++-- 6 files changed, 162 insertions(+), 90 deletions(-) diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index c9f9b9ae5b..94235e3f8e 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -108,7 +108,7 @@ export const RotationForm = observer((props: RotationFormProps) => { const [rotationName, setRotationName] = useState(`[L${layerPriority}] Rotation`); const [isOpen, setIsOpen] = useState(false); - const [offsetTop, setOffsetTop] = useState(0); + const [offsetTop, setOffsetTop] = useState(GRAFANA_HEADER_HEIGHT + 10); const [shiftStart, setShiftStart] = useState(propsShiftStart); const [shiftEnd, setShiftEnd] = useState(propsShiftEnd || shiftStart.add(1, 'day')); @@ -198,7 +198,9 @@ export const RotationForm = observer((props: RotationFormProps) => { } catch (err) { onError(err); } finally { - setIsOpen(true); + setTimeout(() => { + setIsOpen(true); + }, 1000); } }; diff --git a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx index cf79d6006a..a125af4b49 100644 --- a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx @@ -171,7 +171,9 @@ export const ScheduleOverrideForm: FC = (props) => { } catch (err) { onError(err); } finally { - setIsOpen(true); + setTimeout(() => { + setIsOpen(true); + }, 500); } }; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.module.css b/grafana-plugin/src/containers/Rotations/Rotations.module.css index 7bb3634220..67003f21eb 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.module.css +++ b/grafana-plugin/src/containers/Rotations/Rotations.module.css @@ -46,10 +46,6 @@ margin-bottom: 10px; } -.layer-title_first { - margin-top: 40px; -} - .rotations-plus-title { display: flex; flex-direction: column; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 756977c3db..8d3e33f4a2 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -15,7 +15,7 @@ import { RotationForm } from 'containers/RotationForm/RotationForm'; import { TimelineMarks } from 'containers/TimelineMarks/TimelineMarks'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { getColor, getLayersFromStore } from 'models/schedule/schedule.helpers'; -import { Layer, Schedule, ScheduleType, Shift, ShiftSwap, Event } from 'models/schedule/schedule.types'; +import { Schedule, ScheduleType, Shift, ShiftSwap, Event, Layer } from 'models/schedule/schedule.types'; import { ApiSchemas } from 'network/oncall-api/api.types'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -30,8 +30,9 @@ const cx = cn.bind(styles); interface RotationsProps extends WithStoreProps { shiftIdToShowRotationForm?: Shift['id'] | 'new'; + layerPriorityToShowRotationForm?: Layer['priority']; scheduleId: Schedule['id']; - onShowRotationForm: (shiftId: Shift['id'] | 'new') => void; + onShowRotationForm: (shiftId: Shift['id'] | 'new', layerPriority?: Layer['priority']) => void; onClick: (id: Shift['id'] | 'new') => void; onShowOverrideForm: (shiftId: 'new', shiftStart: dayjs.Dayjs, shiftEnd: dayjs.Dayjs) => void; onShowShiftSwapForm: (id: ShiftSwap['id'] | 'new', params?: Partial) => void; @@ -45,7 +46,6 @@ interface RotationsProps extends WithStoreProps { } interface RotationsState { - layerPriority?: Layer['priority']; shiftStartToShowRotationForm?: dayjs.Dayjs; shiftEndToShowRotationForm?: dayjs.Dayjs; } @@ -53,7 +53,6 @@ interface RotationsState { @observer class _Rotations extends Component { state: RotationsState = { - layerPriority: undefined, shiftStartToShowRotationForm: undefined, shiftEndToShowRotationForm: undefined, }; @@ -70,8 +69,9 @@ class _Rotations extends Component { filters, onShowShiftSwapForm, onSlotClick, + layerPriorityToShowRotationForm, } = this.props; - const { layerPriority, shiftStartToShowRotationForm, shiftEndToShowRotationForm } = this.state; + const { shiftStartToShowRotationForm, shiftEndToShowRotationForm } = this.state; const base = 7 * 24 * 60; // in minutes const diff = store.timezoneStore.currentDateInSelectedTimezone.diff( @@ -101,7 +101,7 @@ class _Rotations extends Component { return ( <> -
+
@@ -191,16 +191,14 @@ class _Rotations extends Component { ))} ) : ( -
-
-
- - Layer 1 - -
+
+ +
+
+ + Layer 1 +
-
-
{ @@ -235,7 +233,7 @@ class _Rotations extends Component { shiftId={shiftIdToShowRotationForm} shiftColor={findColor(shiftIdToShowRotationForm, layers)} scheduleId={scheduleId} - layerPriority={layerPriority} + layerPriority={layerPriorityToShowRotationForm} shiftStart={shiftStartToShowRotationForm} shiftEnd={shiftEndToShowRotationForm} onHide={() => { @@ -284,12 +282,9 @@ class _Rotations extends Component { return; } - this.setState( - { layerPriority, shiftStartToShowRotationForm: shiftStart, shiftEndToShowRotationForm: shiftEnd }, - () => { - this.onShowRotationForm('new'); - } - ); + this.setState({ shiftStartToShowRotationForm: shiftStart, shiftEndToShowRotationForm: shiftEnd }, () => { + this.onShowRotationForm('new', layerPriority); + }); }; handleAddRotation = (option: SelectableValue) => { @@ -301,11 +296,10 @@ class _Rotations extends Component { this.setState( { - layerPriority: option.value, shiftStartToShowRotationForm: store.timezoneStore.calendarStartDate, }, () => { - this.onShowRotationForm('new'); + this.onShowRotationForm('new', option.value); } ); }; @@ -313,20 +307,19 @@ class _Rotations extends Component { hideRotationForm = () => { this.setState( { - layerPriority: undefined, shiftStartToShowRotationForm: undefined, shiftEndToShowRotationForm: undefined, }, () => { - this.onShowRotationForm(undefined); + this.onShowRotationForm(undefined, undefined); } ); }; - onShowRotationForm = (shiftId: Shift['id']) => { + onShowRotationForm = (shiftId: Shift['id'], layerPriority?: Layer['priority']) => { const { onShowRotationForm } = this.props; - onShowRotationForm(shiftId); + onShowRotationForm(shiftId, layerPriority); }; handleShowOverrideForm = (shiftStart: dayjs.Dayjs, shiftEnd: dayjs.Dayjs) => { diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index a8726d59b2..30a4466887 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -14,20 +14,18 @@ import { ScheduleOverrideForm } from 'containers/RotationForm/ScheduleOverrideFo import { TimelineMarks } from 'containers/TimelineMarks/TimelineMarks'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { - getLayersFromStore, getOverrideColor, getOverridesFromStore, getShiftSwapsFromStore, SHIFT_SWAP_COLOR, } from 'models/schedule/schedule.helpers'; import { Schedule, Shift, ShiftEvents, ShiftSwap } from 'models/schedule/schedule.types'; -import { getUTCString } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { UserActions } from 'utils/authorization/authorization'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; -import { findClosestUserEvent, findColor } from './Rotations.helpers'; +import { findColor } from './Rotations.helpers'; import styles from './Rotations.module.css'; @@ -39,7 +37,7 @@ interface ScheduleOverridesProps extends WithStoreProps { scheduleId: Schedule['id']; shiftIdToShowRotationForm?: Shift['id'] | 'new'; onShowRotationForm: (shiftId: Shift['id'] | 'new') => void; - onShowShiftSwapForm: (id: ShiftSwap['id'] | 'new', params?: Partial) => void; + onShowShiftSwapForm: (id: ShiftSwap['id'] | 'new') => void; onCreate: () => void; onUpdate: () => void; onDelete: () => void; @@ -74,16 +72,11 @@ class _ScheduleOverrides extends Component { - const closestEvent = findClosestUserEvent(dayjs(), currentUserPk, layers); - const swapStart = closestEvent - ? dayjs(closestEvent.start) - : store.timezoneStore.currentDateInSelectedTimezone.startOf('day').add(1, 'day'); - - const swapEnd = closestEvent ? dayjs(closestEvent.end) : swapStart.add(1, 'day'); - - onShowShiftSwapForm('new', { - swap_start: getUTCString(swapStart), - swap_end: getUTCString(swapEnd), - }); + onShowShiftSwapForm('new'); }} > Request shift swap @@ -151,30 +134,30 @@ class _ScheduleOverrides extends Component {!currentTimeHidden &&
} - - - Swaps - - {shiftSwaps && shiftSwaps.length - ? shiftSwaps.map(({ isPreview, events }, index) => ( - - { - if (event.is_gap) { - return; - } - onShowShiftSwapForm(event.shiftSwapId); - }} - transparent={isPreview} - filters={filters} - /> - - )) - : null} - - + {shiftSwaps && shiftSwaps.length ? ( + + + Swaps + + {shiftSwaps.map(({ isPreview, events }, index) => ( + + { + if (event.is_gap) { + return; + } + onShowShiftSwapForm(event.shiftSwapId); + }} + transparent={isPreview} + filters={filters} + /> + + ))} + + ) : null} + Overrides diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index e5a8cf183f..2466a22e61 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -1,6 +1,17 @@ import React from 'react'; -import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui'; +import { + Button, + HorizontalGroup, + VerticalGroup, + IconButton, + ToolbarButton, + Icon, + Modal, + Dropdown, + Menu, + ButtonGroup, +} from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; @@ -15,6 +26,7 @@ import { Text } from 'components/Text/Text'; import { WithConfirm } from 'components/WithConfirm/WithConfirm'; import { ShiftSwapForm } from 'containers/RotationForm/ShiftSwapForm'; import { Rotations } from 'containers/Rotations/Rotations'; +import { findClosestUserEvent } from 'containers/Rotations/Rotations.helpers'; import { ScheduleFinal } from 'containers/Rotations/ScheduleFinal'; import { ScheduleOverrides } from 'containers/Rotations/ScheduleOverrides'; import { ScheduleForm } from 'containers/ScheduleForm/ScheduleForm'; @@ -22,14 +34,15 @@ import { ScheduleICalSettings } from 'containers/ScheduleIcalLink/ScheduleIcalLi import { UserTimezoneSelect } from 'containers/UserTimezoneSelect/UserTimezoneSelect'; import { UsersTimezones } from 'containers/UsersTimezones/UsersTimezones'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; -import { Event, Schedule, ScheduleType, Shift, ShiftSwap } from 'models/schedule/schedule.types'; +import { getLayersFromStore } from 'models/schedule/schedule.helpers'; +import { Event, Layer, Schedule, ScheduleType, Shift, ShiftSwap } from 'models/schedule/schedule.types'; import { UserHelper } from 'models/user/user.helpers'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; import { isUserActionAllowed, UserActions } from 'utils/authorization/authorization'; import { PLUGIN_ROOT } from 'utils/consts'; -import { getStartOfWeekBasedOnCurrentDate } from './Schedule.helpers'; +import { getStartOfWeekBasedOnCurrentDate, getUTCString } from './Schedule.helpers'; import styles from './Schedule.module.css'; @@ -41,6 +54,7 @@ interface SchedulePageState { schedulePeriodType: string; renderType: string; shiftIdToShowRotationForm?: Shift['id']; + layerPriorityToShowRotationForm: Layer['priority']; shiftIdToShowOverridesForm?: Shift['id']; shiftStartToShowOverrideForm?: dayjs.Dayjs; shiftEndToShowOverrideForm?: dayjs.Dayjs; @@ -65,6 +79,7 @@ class _SchedulePage extends React.Component )} + + {layers?.map((layer, index) => ( + { + document + .getElementById('rotations') + .scrollIntoView({ behavior: 'smooth', block: 'center' }); + + this.handleShowRotationForm('new', layer.priority); + }} + /> + ))} + { + document + .getElementById('rotations') + .scrollIntoView({ behavior: 'smooth', block: 'center' }); + + this.handleShowRotationForm('new', nextPriority); + }} + /> + { + document + .getElementById('overrides-list') + .scrollIntoView({ behavior: 'smooth', block: 'center' }); + + this.handleShowShiftSwapForm('new'); + }} + /> + { + document + .getElementById('overrides-list') + .scrollIntoView({ behavior: 'smooth', block: 'center' }); + + this.handleShowOverridesForm('new'); + }} + /> + + } + > + + + - + {store.timezoneStore.calendarStartDate.format('DD MMM')} -{' '} {store.timezoneStore.calendarStartDate.add(6, 'day').format('DD MMM')} diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 50a99b5c37..c5576150cc 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -80,7 +80,10 @@ class _SchedulesPage extends React.Component Schedules
- + + View in timezone: + +
) : ( - @@ -132,12 +129,12 @@ class _Rotations extends Component { label="Add rotation" options={options} onChange={this.handleAddRotation} - variant="primary" + variant="secondary" size="md" /> ) : (
{layers && layers.length ? ( - + {!currentTimeHidden && ( -
+
)} {layers.map((layer, layerIndex) => ( @@ -163,7 +165,7 @@ class _Rotations extends Component {
- + {layer.shifts.map(({ shiftId, isPreview, events }, rotationIndex) => ( { ))} ) : ( -
+
- - Layer 1 + + + Layer 1 +
-
- { - this.handleAddLayer(nextPriority, shiftStart, shiftEnd); - }} - events={[]} - layerIndex={0} - rotationIndex={0} - /> -
+ { + this.handleAddLayer(nextPriority, shiftStart, shiftEnd); + }} + events={[]} + layerIndex={0} + rotationIndex={0} + />
diff --git a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx index a2f3743bf2..0d75baa51c 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx @@ -79,20 +79,18 @@ const _ScheduleFinal: FC = observer( {!simplified && (
-
- - Final schedule - -
+ + Final schedule +
)}
{rows.map(({ startDate }, index) => ( - - + +
1; const schedule = store.scheduleStore.items[scheduleId]; @@ -99,11 +98,9 @@ class _ScheduleOverrides extends Component
-
- - Overrides and swaps - -
+ + Overrides and swaps +
-
+
{!currentTimeHidden &&
} {shiftSwaps && shiftSwaps.length ? ( - + Swaps @@ -160,7 +157,7 @@ class _ScheduleOverrides extends Component ) : null} - + Overrides diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss index bd73516db3..c4198028ca 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss @@ -1,7 +1,6 @@ .root { position: absolute; display: flex; - z-index: 1; width: 100%; top: 0; bottom: 0; @@ -11,6 +10,10 @@ pointer-events: none; } +.root--borderBottom { + border-bottom: var(--border-medium); +} + .weekday { width: calc(100% / 7); display: flex; @@ -28,6 +31,14 @@ } } +.weekday:not(:last-child) { + border-right: var(--border-medium); +} + +.weekday--weekEnd { + background: var(--background-disabled); +} + .weekday-title { width: 100%; text-align: center; @@ -35,10 +46,6 @@ flex-grow: 1; } -.weekday:not(:last-child) .weekday-title { - border-right: var(--border-medium); -} - .weekday-times { width: 100%; display: flex; diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx index 6cd2917898..08ac985c0d 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx @@ -13,6 +13,7 @@ import styles from './TimelineMarks.module.scss'; interface TimelineMarksProps { debug?: boolean; startDate?: dayjs.Dayjs; + withBorderBottom?: boolean; } const cx = cn.bind(styles); @@ -22,7 +23,7 @@ export const TimelineMarks: FC = observer((props) => { timezoneStore: { currentDateInSelectedTimezone, calendarStartDate }, scheduleStore, } = useStore(); - const { debug, startDate: propsStartDate } = props; + const { debug, startDate: propsStartDate, withBorderBottom = false } = props; const startDate = propsStartDate || calendarStartDate; @@ -55,7 +56,7 @@ export const TimelineMarks: FC = observer((props) => { }, [days]); return ( -
+
{debug && ( {cuts.map((_cut, index) => ( @@ -75,10 +76,10 @@ export const TimelineMarks: FC = observer((props) => { {momentsToRender.map((m, i) => { const isCurrentDay = currentDateInSelectedTimezone.isSame(m.moment, 'day'); - // const isWeekend = m.moment.day() == 0 || m.moment.day() === 6; + const isWeekend = m.moment.day() === 0 || m.moment.day() === 6; return ( -
+
{m.moment.date() === 1 ? m.moment.format('ddd D MMM') : m.moment.format('ddd D')} diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 074d1e0fed..eea4035fff 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -41,6 +41,7 @@ import { Event, Layer, Schedule, ScheduleType, ScheduleView, Shift, ShiftSwap } import { UserHelper } from 'models/user/user.helpers'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; +import { scrollToElement } from 'utils/DOM'; import { isUserActionAllowed, UserActions } from 'utils/authorization/authorization'; import { PLUGIN_ROOT } from 'utils/consts'; @@ -229,9 +230,7 @@ class _SchedulePage extends React.Component { - document - .getElementById('rotations') - .scrollIntoView({ behavior: 'instant', block: 'center' }); + scrollToElement(document.getElementById('rotations')); this.handleShowRotationForm('new', layer.priority); }} @@ -240,9 +239,7 @@ class _SchedulePage extends React.Component { - document - .getElementById('rotations') - .scrollIntoView({ behavior: 'instant', block: 'center' }); + scrollToElement(document.getElementById('rotations')); this.handleShowRotationForm('new', nextPriority); }} @@ -250,19 +247,13 @@ class _SchedulePage extends React.Component { - document - .getElementById('overrides-list') - .scrollIntoView({ behavior: 'instant', block: 'center' }); - this.handleShowShiftSwapForm('new'); }} /> { - document - .getElementById('overrides-list') - .scrollIntoView({ behavior: 'instant', block: 'center' }); + scrollToElement(document.getElementById('overrides-list')); this.handleShowOverridesForm('new'); }} diff --git a/grafana-plugin/src/utils/DOM.ts b/grafana-plugin/src/utils/DOM.ts index 2fc58cb507..fcf08d8eff 100644 --- a/grafana-plugin/src/utils/DOM.ts +++ b/grafana-plugin/src/utils/DOM.ts @@ -18,6 +18,10 @@ export const waitForElement = (selector: string) => { }); }; +export const scrollToElement = (element: Element, behavior: ScrollBehavior = 'instant' as ScrollBehavior) => { + document.getElementById('rotations').scrollIntoView({ behavior, block: 'center' }); +}; + export const getCoords = (elem) => { // crossbrowser version const box = elem.getBoundingClientRect(); From 595ceb2791a1f7dc71fcd6e2615e0b2c274d7ba6 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Tue, 21 May 2024 16:46:16 +0100 Subject: [PATCH 09/28] polixh schedules styling --- grafana-plugin/src/assets/style/vars.css | 2 +- grafana-plugin/src/containers/Rotation/Rotation.module.css | 2 +- .../src/containers/Rotations/Rotations.module.css | 2 +- .../src/containers/ScheduleSlot/ScheduleSlot.module.css | 6 ++---- .../src/containers/UsersTimezones/UsersTimezones.tsx | 2 +- grafana-plugin/src/pages/schedule/Schedule.tsx | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/grafana-plugin/src/assets/style/vars.css b/grafana-plugin/src/assets/style/vars.css index 725d1a0e4b..d38c7811de 100644 --- a/grafana-plugin/src/assets/style/vars.css +++ b/grafana-plugin/src/assets/style/vars.css @@ -34,7 +34,7 @@ --background-canvas: #f4f5f5; --background-primary: #fff; --background-secondary: #f4f5f5; - --background-disabled: rgba(204, 204, 220, 0.04); + --background-disabled: rgba(204, 204, 220, 0.11); --border-medium-color: rgba(36, 41, 46, 0.3); --border-medium: 1px solid rgba(36, 41, 46, 0.3); --border-strong: 1px solid rgba(36, 41, 46, 0.4); diff --git a/grafana-plugin/src/containers/Rotation/Rotation.module.css b/grafana-plugin/src/containers/Rotation/Rotation.module.css index b217c5b50b..ed1d8f1f17 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.module.css +++ b/grafana-plugin/src/containers/Rotation/Rotation.module.css @@ -40,7 +40,7 @@ } .slots__animate { - /* transition: transform 500ms ease; TODO either remove or uncomment */ + /* transition: transform 500ms ease; */ } .current-time { diff --git a/grafana-plugin/src/containers/Rotations/Rotations.module.css b/grafana-plugin/src/containers/Rotations/Rotations.module.css index bb53d23392..4ba49e4855 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.module.css +++ b/grafana-plugin/src/containers/Rotations/Rotations.module.css @@ -21,7 +21,7 @@ } .layer { - padding-bottom: 10px; + padding-bottom: 8px; } .layer-first { diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css index 6243b495f1..82e4f38bd8 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css @@ -5,7 +5,7 @@ position: relative; display: flex; overflow: hidden; - margin: 0 1px; + margin-right: 1px; padding: 4px; align-items: center; transition: opacity 0.2s ease; @@ -34,9 +34,7 @@ } .root__type_shift-swap { - border-radius: 10px; - background: #ff99002e; - height: 20px; + background: rgba(255, 153, 0, 0.18); } .no-user { diff --git a/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.tsx b/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.tsx index 7ec461ccb1..97a6ed602b 100644 --- a/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.tsx +++ b/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.tsx @@ -83,7 +83,7 @@ export const UsersTimezones: FC = observer((props) => {
- + Schedule team and timezones
diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index eea4035fff..373c08c653 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -308,7 +308,7 @@ class _SchedulePage extends React.Component
- + {store.timezoneStore.calendarStartDate.format('DD MMM')} -{' '} {store.timezoneStore.calendarStartDate .add( From 7b3e232022840e321beec880c76db4e976b5c4f1 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Wed, 22 May 2024 14:18:12 +0100 Subject: [PATCH 10/28] minor polishing --- .../containers/RotationForm/RotationForm.tsx | 3 +- .../RotationForm/ScheduleOverrideForm.tsx | 3 +- .../containers/Rotations/Rotations.module.css | 1 - .../containers/Rotations/ScheduleFinal.tsx | 27 ++++++++++---- .../containers/Rotations/SchedulePersonal.tsx | 36 +++++++++++++------ .../src/models/schedule/schedule.ts | 9 ++--- .../src/pages/schedule/Schedule.tsx | 13 ++++--- .../src/pages/schedules/Schedules.tsx | 9 +++-- 8 files changed, 68 insertions(+), 33 deletions(-) diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index 498fb4fa09..4958285d24 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -198,9 +198,10 @@ export const RotationForm = observer((props: RotationFormProps) => { } catch (err) { onError(err); } finally { + // wait until a scroll to the "Rotations" happened setTimeout(() => { setIsOpen(true); - }, 1000); + }, 100); } }; diff --git a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx index a125af4b49..d28e510a2a 100644 --- a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx @@ -171,9 +171,10 @@ export const ScheduleOverrideForm: FC = (props) => { } catch (err) { onError(err); } finally { + // wait until a scroll to the "Overrides and swaps" happened setTimeout(() => { setIsOpen(true); - }, 500); + }, 100); } }; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.module.css b/grafana-plugin/src/containers/Rotations/Rotations.module.css index 4ba49e4855..ffabb48bc7 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.module.css +++ b/grafana-plugin/src/containers/Rotations/Rotations.module.css @@ -17,7 +17,6 @@ top: 0; bottom: 0; z-index: 1; - transition: left 500ms ease; } .layer { diff --git a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx index c92a64ce31..8adbb1873b 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx @@ -18,7 +18,7 @@ import { getTotalDaysToDisplay, scheduleViewToDaysInOneRow, } from 'models/schedule/schedule.helpers'; -import { Event, Schedule, ShiftSwap } from 'models/schedule/schedule.types'; +import { Event, Schedule, ScheduleView, ShiftSwap } from 'models/schedule/schedule.types'; import { getCurrentTimeX } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -38,14 +38,27 @@ interface ScheduleFinalProps extends WithStoreProps { disabled?: boolean; filters: ScheduleFiltersType; onSlotClick?: (event: Event) => void; + scheduleView?: ScheduleView; } const _ScheduleFinal: FC = observer( - ({ store, simplified, scheduleId, filters, onShowShiftSwapForm, onShowOverrideForm, onSlotClick }) => { + ({ + store, + simplified, + scheduleId, + filters, + onShowShiftSwapForm, + onShowOverrideForm, + onSlotClick, + scheduleView: propsScheduleView, + }) => { const { timezoneStore: { currentDateInSelectedTimezone, calendarStartDate }, + scheduleStore: { scheduleView: storeScheduleView }, } = store; + const scheduleView = propsScheduleView || storeScheduleView; + const shifts = flattenShiftEvents(getShiftsFromStore(store, scheduleId, calendarStartDate)); const layers = getLayersFromStore(store, scheduleId, calendarStartDate); @@ -59,15 +72,15 @@ const _ScheduleFinal: FC = observer( }; const rows = useMemo(() => { - const totalDays = getTotalDaysToDisplay(store.scheduleStore.scheduleView, calendarStartDate); + const totalDays = getTotalDaysToDisplay(scheduleView, calendarStartDate); const rows = []; - for (let i = 0; i < totalDays / scheduleViewToDaysInOneRow[store.scheduleStore.scheduleView]; i++) { + for (let i = 0; i < totalDays / scheduleViewToDaysInOneRow[scheduleView]; i++) { rows.push({ - startDate: calendarStartDate.add(scheduleViewToDaysInOneRow[store.scheduleStore.scheduleView] * i, 'days'), + startDate: calendarStartDate.add(scheduleViewToDaysInOneRow[scheduleView] * i, 'days'), }); } return rows; - }, [calendarStartDate, store.scheduleStore.scheduleView]); + }, [calendarStartDate, scheduleView]); return (
@@ -91,7 +104,7 @@ const _ScheduleFinal: FC = observer( getCurrentTimeX( currentDateInSelectedTimezone, startDate, - scheduleViewToDaysInOneRow[store.scheduleStore.scheduleView] * 24 * 60 + scheduleViewToDaysInOneRow[scheduleView] * 24 * 60 ) * 100 }%`, }} diff --git a/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx b/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx index f4b95311c7..7faea66ebd 100644 --- a/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx +++ b/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx @@ -11,10 +11,15 @@ import { Text } from 'components/Text/Text'; import { Rotation } from 'containers/Rotation/Rotation'; import { TimelineMarks } from 'containers/TimelineMarks/TimelineMarks'; import { ActionKey } from 'models/loader/action-keys'; -import { getColorForSchedule, getPersonalShiftsFromStore } from 'models/schedule/schedule.helpers'; -import { Event } from 'models/schedule/schedule.types'; +import { + getColorForSchedule, + getPersonalShiftsFromStore, + getTotalDaysToDisplay, + scheduleViewToDaysInOneRow, +} from 'models/schedule/schedule.helpers'; +import { Event, ScheduleView } from 'models/schedule/schedule.types'; import { ApiSchemas } from 'network/oncall-api/api.types'; -import { getStartOfWeekBasedOnCurrentDate } from 'pages/schedule/Schedule.helpers'; +import { getCurrentTimeX, getStartOfWeekBasedOnCurrentDate } from 'pages/schedule/Schedule.helpers'; import { useStore } from 'state/useStore'; import { PLUGIN_ROOT } from 'utils/consts'; import { useIsLoading } from 'utils/hooks'; @@ -40,7 +45,7 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC }, [timezoneStore.selectedTimezoneOffset]); const updatePersonalEvents = () => { - scheduleStore.updatePersonalEvents(userStore.currentUserPk, timezoneStore.calendarStartDate, 9, true); + scheduleStore.updatePersonalEvents(userStore.currentUserPk, timezoneStore.calendarStartDate, true); }; const handleTodayClick = () => { @@ -48,12 +53,22 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC }; const handleLeftClick = () => { - timezoneStore.setCalendarStartDate(timezoneStore.calendarStartDate.subtract(7, 'day')); + timezoneStore.setCalendarStartDate( + timezoneStore.calendarStartDate.subtract( + getTotalDaysToDisplay(ScheduleView.OneWeek, store.timezoneStore.calendarStartDate), + 'day' + ) + ); scheduleStore.updatePersonalEvents(userStore.currentUserPk, timezoneStore.calendarStartDate); }; const handleRightClick = () => { - timezoneStore.setCalendarStartDate(timezoneStore.calendarStartDate.add(7, 'day')); + timezoneStore.setCalendarStartDate( + timezoneStore.calendarStartDate.add( + getTotalDaysToDisplay(ScheduleView.OneWeek, store.timezoneStore.calendarStartDate), + 'day' + ) + ); scheduleStore.updatePersonalEvents(userStore.currentUserPk, timezoneStore.calendarStartDate); }; @@ -61,10 +76,11 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC history.push(`${PLUGIN_ROOT}/schedules/${event.schedule?.id}`); }; - const base = 7 * 24 * 60; // in minutes - const diff = timezoneStore.currentDateInSelectedTimezone.diff(timezoneStore.calendarStartDate, 'minutes'); - - const currentTimeX = diff / base; + const currentTimeX = getCurrentTimeX( + timezoneStore.currentDateInSelectedTimezone, + timezoneStore.calendarStartDate, + scheduleViewToDaysInOneRow[ScheduleView.OneWeek] * 24 * 60 + ); const shifts = getPersonalShiftsFromStore(store, userPk, timezoneStore.calendarStartDate); diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index a3beb72c50..d535e169f4 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -654,14 +654,11 @@ export class ScheduleStore extends BaseStore { @AutoLoadingState(ActionKey.UPDATE_PERSONAL_EVENTS) @action.bound - async updatePersonalEvents( - userPk: ApiSchemas['User']['pk'], - startMoment: dayjs.Dayjs, - days = 9, - isUpdateOnCallNow = false - ) { + async updatePersonalEvents(userPk: ApiSchemas['User']['pk'], startMoment: dayjs.Dayjs, isUpdateOnCallNow = false) { const fromString = getFromString(startMoment); + const days = getTotalDaysToDisplay(ScheduleView.OneWeek, this.rootStore.timezoneStore.calendarStartDate) + 1; + const dayBefore = startMoment.subtract(1, 'day'); const { is_oncall, schedules } = await makeRequest(`/schedules/current_user_events/`, { diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 43264dc9b4..abd31b0e2d 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -20,7 +20,6 @@ import { RouteComponentProps, withRouter } from 'react-router-dom'; import { PageErrorHandlingWrapper } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper'; import { PluginLink } from 'components/PluginLink/PluginLink'; -import { RenderConditionally } from 'components/RenderConditionally/RenderConditionally'; import { ScheduleFilters } from 'components/ScheduleFilters/ScheduleFilters'; import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types'; import { ScheduleQuality } from 'components/ScheduleQuality/ScheduleQuality'; @@ -247,6 +246,8 @@ class _SchedulePage extends React.Component { + scrollToElement(document.getElementById('overrides-list')); + this.handleShowShiftSwapForm('new'); }} /> @@ -368,7 +369,8 @@ class _SchedulePage extends React.Component - + {/* we need to render to allow Rotations show rotaion modal form */} +
- - +
+ {/* we need to render to allow ScheduleOverrides show overrides modal form */} +
- +
)} diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 46a1382dd4..4712779217 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -23,7 +23,7 @@ import { TeamName } from 'containers/TeamName/TeamName'; import { TimelineMarks } from 'containers/TimelineMarks/TimelineMarks'; import { UserTimezoneSelect } from 'containers/UserTimezoneSelect/UserTimezoneSelect'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; -import { Schedule } from 'models/schedule/schedule.types'; +import { Schedule, ScheduleView } from 'models/schedule/schedule.types'; import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers'; import { WithStoreProps, PageProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -188,7 +188,12 @@ class _SchedulesPage extends React.Component
- +
); From b1483e2d773d20ca0ab4eb3b37bd30bed297f4f0 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 23 May 2024 13:38:32 +0100 Subject: [PATCH 11/28] display pages/Schedules in 7 days mode always --- .../src/containers/Rotation/Rotation.tsx | 13 +++++++++---- .../src/containers/Rotations/ScheduleFinal.tsx | 9 +++++++-- .../containers/Rotations/SchedulePersonal.tsx | 3 ++- .../src/containers/ScheduleSlot/ScheduleSlot.tsx | 10 +++++++--- .../TimelineMarks/TimelineMarks.module.scss | 11 ----------- .../containers/TimelineMarks/TimelineMarks.tsx | 11 +++++++---- .../src/models/schedule/schedule.helpers.ts | 6 +++--- grafana-plugin/src/pages/schedules/Schedules.tsx | 16 ++++++---------- 8 files changed, 41 insertions(+), 38 deletions(-) diff --git a/grafana-plugin/src/containers/Rotation/Rotation.tsx b/grafana-plugin/src/containers/Rotation/Rotation.tsx index 1f5cc5988c..aae75ca627 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.tsx +++ b/grafana-plugin/src/containers/Rotation/Rotation.tsx @@ -10,7 +10,7 @@ import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters. import { Text } from 'components/Text/Text'; import { ScheduleSlot } from 'containers/ScheduleSlot/ScheduleSlot'; import { scheduleViewToDaysInOneRow } from 'models/schedule/schedule.helpers'; -import { Event, ShiftSwap } from 'models/schedule/schedule.types'; +import { Event, ScheduleView, ShiftSwap } from 'models/schedule/schedule.types'; import { useStore } from 'state/useStore'; import styles from './Rotation.module.css'; @@ -35,12 +35,13 @@ interface RotationProps { emptyText?: string; showScheduleNameAsSlotTitle?: boolean; startDate?: dayjs.Dayjs; + scheduleView?: ScheduleView; } export const Rotation: FC = observer((props) => { const { timezoneStore: { calendarStartDate, getDateInSelectedTimezone }, - scheduleStore, + scheduleStore: { scheduleView: storeScheduleView }, } = useStore(); const { events, @@ -58,11 +59,14 @@ export const Rotation: FC = observer((props) => { emptyText, showScheduleNameAsSlotTitle, startDate: propsStartDate, + scheduleView: propsScheduleView, } = props; + const scheduleView = propsScheduleView || storeScheduleView; + const startDate = propsStartDate || calendarStartDate; - const days = scheduleViewToDaysInOneRow[scheduleStore.scheduleView]; + const days = scheduleViewToDaysInOneRow[scheduleView]; const [animate, _setAnimate] = useState(true); @@ -71,7 +75,7 @@ export const Rotation: FC = observer((props) => { const x = event.clientX - rect.left; //x position within the element. const width = event.currentTarget.offsetWidth; - const dayOffset = Math.floor((x / width) * scheduleViewToDaysInOneRow[scheduleStore.scheduleView]); + const dayOffset = Math.floor((x / width) * scheduleViewToDaysInOneRow[scheduleView]); const shiftStart = startDate.add(dayOffset, 'day'); const shiftEnd = shiftStart.add(1, 'day'); @@ -156,6 +160,7 @@ export const Rotation: FC = observer((props) => { {events.map((event) => { return ( = observer(
{rows.map(({ startDate }, index) => ( - +
= observer( return ( = observer( }) ) : ( - + )} diff --git a/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx b/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx index 7faea66ebd..d74859dde8 100644 --- a/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx +++ b/grafana-plugin/src/containers/Rotations/SchedulePersonal.tsx @@ -134,13 +134,14 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC
{!currentTimeHidden &&
} - + {shifts?.length ? ( shifts.map(({ events }, index) => { return ( ) => void; showScheduleNameAsSlotTitle?: boolean; + scheduleView?: ScheduleView; } const cx = cn.bind(styles); @@ -38,7 +39,7 @@ const cx = cn.bind(styles); export const ScheduleSlot: FC = observer((props) => { const { timezoneStore: { getDateInSelectedTimezone }, - scheduleStore, + scheduleStore: { scheduleView: storeScheduleView }, } = useStore(); const { event, @@ -50,14 +51,17 @@ export const ScheduleSlot: FC = observer((props) => { filters, onClick, showScheduleNameAsSlotTitle, + scheduleView: propsScheduleView, } = props; + const scheduleView = propsScheduleView || storeScheduleView; + const start = getDateInSelectedTimezone(event.start); const end = getDateInSelectedTimezone(event.end); const durationInSeconds = end.diff(start, 'seconds'); - const rowInSeconds = scheduleViewToDaysInOneRow[scheduleStore.scheduleView] * 24 * 60 * 60; + const rowInSeconds = scheduleViewToDaysInOneRow[scheduleView] * 24 * 60 * 60; const width = Math.max(durationInSeconds / rowInSeconds, 0); diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss index c4198028ca..d572d3fc14 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.module.scss @@ -15,20 +15,9 @@ } .weekday { - width: calc(100% / 7); display: flex; flex-direction: column; justify-content: space-between; - - &--weekend { - background: repeating-linear-gradient( - -45deg, - var(--background-canvas), - var(--background-canvas) 5px, - transparent 5px, - transparent 8px - ); - } } .weekday:not(:last-child) { diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx index 08ac985c0d..605df70941 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx @@ -6,6 +6,7 @@ import { observer } from 'mobx-react'; import { Text } from 'components/Text/Text'; import { scheduleViewToDaysInOneRow } from 'models/schedule/schedule.helpers'; +import { ScheduleView } from 'models/schedule/schedule.types'; import { useStore } from 'state/useStore'; import styles from './TimelineMarks.module.scss'; @@ -14,6 +15,7 @@ interface TimelineMarksProps { debug?: boolean; startDate?: dayjs.Dayjs; withBorderBottom?: boolean; + scheduleView?: ScheduleView; } const cx = cn.bind(styles); @@ -21,13 +23,14 @@ const cx = cn.bind(styles); export const TimelineMarks: FC = observer((props) => { const { timezoneStore: { currentDateInSelectedTimezone, calendarStartDate }, - scheduleStore, + scheduleStore: { scheduleView: storeScheduleView }, } = useStore(); - const { debug, startDate: propsStartDate, withBorderBottom = false } = props; + const { debug, startDate: propsStartDate, withBorderBottom = false, scheduleView: propsScheduleView } = props; const startDate = propsStartDate || calendarStartDate; + const scheduleView = propsScheduleView || storeScheduleView; - const days = scheduleViewToDaysInOneRow[scheduleStore.scheduleView]; + const days = scheduleViewToDaysInOneRow[scheduleView]; const momentsToRender = useMemo(() => { const hoursToSplit = 12; @@ -79,7 +82,7 @@ export const TimelineMarks: FC = observer((props) => { const isWeekend = m.moment.day() === 0 || m.moment.day() === 6; return ( -
+
{m.moment.date() === 1 ? m.moment.format('ddd D MMM') : m.moment.format('ddd D')} diff --git a/grafana-plugin/src/models/schedule/schedule.helpers.ts b/grafana-plugin/src/models/schedule/schedule.helpers.ts index e22de095a3..bb390e6cc9 100644 --- a/grafana-plugin/src/models/schedule/schedule.helpers.ts +++ b/grafana-plugin/src/models/schedule/schedule.helpers.ts @@ -423,13 +423,13 @@ export const enrichOverrides = ( const L1_COLORS = ['#3D71D9', '#6D609C', '#4D3B72', '#8214A0']; -const L2_COLORS = ['#3CB979', '#188343', '#84362A', '#521913']; +const L2_COLORS = ['#517A00', '#3F6833', '#84362A', '#521913']; const L3_COLORS = ['#377277', '#638282', '#364E4E', '#423220']; -const OVERRIDE_COLORS = ['#C69B06', '#C2C837']; +const OVERRIDE_COLORS = ['#C69B06']; -export const SHIFT_SWAP_COLOR = '#C69B06'; +export const SHIFT_SWAP_COLOR = '#D66310'; const COLORS = [L1_COLORS, L2_COLORS, L3_COLORS]; diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 4712779217..f6d490cd77 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -20,7 +20,6 @@ import { ScheduleFinal } from 'containers/Rotations/ScheduleFinal'; import { SchedulePersonal } from 'containers/Rotations/SchedulePersonal'; import { ScheduleForm } from 'containers/ScheduleForm/ScheduleForm'; import { TeamName } from 'containers/TeamName/TeamName'; -import { TimelineMarks } from 'containers/TimelineMarks/TimelineMarks'; import { UserTimezoneSelect } from 'containers/UserTimezoneSelect/UserTimezoneSelect'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; import { Schedule, ScheduleView } from 'models/schedule/schedule.types'; @@ -186,15 +185,12 @@ class _SchedulesPage extends React.Component (
- -
- -
+
); From 49551c8581f8c49545125b2fee2cc07617e56928 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 23 May 2024 15:08:41 +0100 Subject: [PATCH 12/28] request one week events from schedules list --- grafana-plugin/src/models/schedule/schedule.ts | 7 ++++--- grafana-plugin/src/pages/schedules/Schedules.tsx | 13 ++++--------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index d535e169f4..24297a4fa5 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -124,7 +124,7 @@ export class ScheduleStore extends BaseStore { }; @observable - scheduleView = ScheduleView.OneWeek; + scheduleView = ScheduleView.TwoWeeks; constructor(rootStore: RootStore) { super(rootStore); @@ -533,11 +533,12 @@ export class ScheduleStore extends BaseStore { } @action.bound - async refreshEvents(scheduleId: string) { + async refreshEvents(scheduleId: string, scheduleView?: ScheduleView) { this.refreshEventsError = {}; const startMoment = this.rootStore.timezoneStore.calendarStartDate; - const days = getTotalDaysToDisplay(this.scheduleView, this.rootStore.timezoneStore.calendarStartDate) + 1; + const days = + getTotalDaysToDisplay(scheduleView || this.scheduleView, this.rootStore.timezoneStore.calendarStartDate) + 1; try { const schedule = await this.loadItem(scheduleId); diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index f6d490cd77..4d9f01089e 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -167,7 +167,7 @@ class _SchedulesPage extends React.Component { - this.props.store.scheduleStore.refreshEvents(data.id); + this.props.store.scheduleStore.refreshEvents(data.id, ScheduleView.OneWeek); }); } else if (!expanded && expandedRowKeys.includes(data.id)) { const index = expandedRowKeys.indexOf(data.id); @@ -180,7 +180,9 @@ class _SchedulesPage extends React.Component { const { expandedRowKeys } = this.state; - expandedRowKeys.forEach(this.props.store.scheduleStore.refreshEvents); + expandedRowKeys.forEach((key: Schedule['id']) => { + this.props.store.scheduleStore.refreshEvents(key, ScheduleView.OneWeek); + }); }; renderSchedule = (data: Schedule) => ( @@ -370,13 +372,6 @@ class _SchedulesPage extends React.Component Date: Thu, 23 May 2024 15:11:31 +0100 Subject: [PATCH 13/28] change scheduleView to one week by default --- grafana-plugin/src/models/schedule/schedule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index 24297a4fa5..03cdc0788b 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -124,7 +124,7 @@ export class ScheduleStore extends BaseStore { }; @observable - scheduleView = ScheduleView.TwoWeeks; + scheduleView = ScheduleView.OneWeek; constructor(rootStore: RootStore) { super(rootStore); From 23b674a4e1b2495c85d34ec0698b7ac5185aaf9c Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 23 May 2024 15:37:49 +0100 Subject: [PATCH 14/28] add Date picker to the schedule page --- .../src/models/schedule/schedule.helpers.ts | 4 ++-- .../src/pages/schedule/Schedule.tsx | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/grafana-plugin/src/models/schedule/schedule.helpers.ts b/grafana-plugin/src/models/schedule/schedule.helpers.ts index bb390e6cc9..e09eeceb13 100644 --- a/grafana-plugin/src/models/schedule/schedule.helpers.ts +++ b/grafana-plugin/src/models/schedule/schedule.helpers.ts @@ -427,9 +427,9 @@ const L2_COLORS = ['#517A00', '#3F6833', '#84362A', '#521913']; const L3_COLORS = ['#377277', '#638282', '#364E4E', '#423220']; -const OVERRIDE_COLORS = ['#C69B06']; +const OVERRIDE_COLORS = ['#EF9C48']; -export const SHIFT_SWAP_COLOR = '#D66310'; +export const SHIFT_SWAP_COLOR = '#DC7532'; const COLORS = [L1_COLORS, L2_COLORS, L3_COLORS]; diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index abd31b0e2d..743c493ed2 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -12,6 +12,7 @@ import { Menu, ButtonGroup, RadioButtonGroup, + DatePicker, } from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; @@ -67,6 +68,7 @@ interface SchedulePageState { filters: ScheduleFiltersType; shiftSwapIdToShowForm?: ShiftSwap['id'] | 'new'; shiftSwapParamsToShowForm?: Partial; + calendarStartDatePickerIsOpen: boolean; } @observer @@ -88,6 +90,7 @@ class _SchedulePage extends React.Component + { + this.setState({ calendarStartDatePickerIsOpen: !calendarStartDatePickerIsOpen }); + }} + /> + { + store.timezoneStore.setCalendarStartDate( + getCalendarStartDate(dayjs(newDate), scheduleView) + ); + this.handleDateRangeUpdate(); + }} + onClose={() => this.setState({ calendarStartDatePickerIsOpen: false })} + /> Date: Fri, 24 May 2024 10:33:07 +0100 Subject: [PATCH 15/28] sligthly update layers colors --- grafana-plugin/src/models/schedule/schedule.helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/models/schedule/schedule.helpers.ts b/grafana-plugin/src/models/schedule/schedule.helpers.ts index e09eeceb13..be78e98ccf 100644 --- a/grafana-plugin/src/models/schedule/schedule.helpers.ts +++ b/grafana-plugin/src/models/schedule/schedule.helpers.ts @@ -423,7 +423,7 @@ export const enrichOverrides = ( const L1_COLORS = ['#3D71D9', '#6D609C', '#4D3B72', '#8214A0']; -const L2_COLORS = ['#517A00', '#3F6833', '#84362A', '#521913']; +const L2_COLORS = ['#299C46', '#517A00', '#84362A', '#521913']; const L3_COLORS = ['#377277', '#638282', '#364E4E', '#423220']; From 1a2b954a4231500a0b34f05c0e5a0182aa2ff0ac Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Fri, 24 May 2024 13:00:59 +0100 Subject: [PATCH 16/28] change shift swaps appearance --- .../src/containers/ScheduleSlot/ScheduleSlot.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx index a227b010a8..a187c697e2 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx @@ -268,8 +268,13 @@ const RegularEvent = (props: RegularEventProps) => { const title = isShiftSwap ? 'Shift swap' : showScheduleNameAsSlotTitle ? schedule?.name : getShiftName(shift); let backgroundColor = color; + let border = undefined; if (isShiftSwap) { backgroundColor = SHIFT_SWAP_COLOR; + if (!swap_request.user) { + backgroundColor = 'transparent'; + border = `1px solid ${SHIFT_SWAP_COLOR}`; + } } const scheduleSlotContent = ( @@ -277,6 +282,7 @@ const RegularEvent = (props: RegularEventProps) => { className={cx('root', { root__inactive: inactive })} style={{ backgroundColor, + border, }} onClick={swap_request ? getShiftSwapClickHandler(swap_request.pk) : undefined} data-testid="schedule-slot" @@ -291,7 +297,13 @@ const RegularEvent = (props: RegularEventProps) => { /> )}
- {swap_request && !swap_request.user ? : userTitle} + {swap_request ? ( + + {swap_request.user ? swap_request.user.display_name + ' → ' + userTitle : userTitle + ' → ?'} + + ) : ( + userTitle + )}
); From c32e3408ff9f52ff7f787a5538990f0651797a15 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Fri, 24 May 2024 13:27:12 +0100 Subject: [PATCH 17/28] change shift swaps appearance 2 --- .../containers/ScheduleSlot/ScheduleSlot.module.css | 1 - .../src/containers/ScheduleSlot/ScheduleSlot.tsx | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css index 82e4f38bd8..df1d8b15f4 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css @@ -52,7 +52,6 @@ .title { z-index: 1; - color: #fff; font-size: 12px; width: 100%; font-weight: 500; diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx index a187c697e2..6a53635283 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx @@ -269,11 +269,13 @@ const RegularEvent = (props: RegularEventProps) => { let backgroundColor = color; let border = undefined; + let textColor = '#fff'; if (isShiftSwap) { backgroundColor = SHIFT_SWAP_COLOR; if (!swap_request.user) { backgroundColor = 'transparent'; border = `1px solid ${SHIFT_SWAP_COLOR}`; + textColor = SHIFT_SWAP_COLOR; } } @@ -283,6 +285,7 @@ const RegularEvent = (props: RegularEventProps) => { style={{ backgroundColor, border, + color: textColor, }} onClick={swap_request ? getShiftSwapClickHandler(swap_request.pk) : undefined} data-testid="schedule-slot" @@ -296,15 +299,7 @@ const RegularEvent = (props: RegularEventProps) => { duration={duration} /> )} -
- {swap_request ? ( - - {swap_request.user ? swap_request.user.display_name + ' → ' + userTitle : userTitle + ' → ?'} - - ) : ( - userTitle - )} -
+
{swap_request && !swap_request.user ? userTitle + ' → ?' : userTitle}
); From e86bd6992bc5af73a0ce131ad164d74cfc008e8e Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Fri, 24 May 2024 13:47:09 +0100 Subject: [PATCH 18/28] minor UI polishing --- grafana-plugin/src/containers/Rotation/Rotation.module.css | 4 ---- grafana-plugin/src/containers/Rotation/Rotation.tsx | 6 ++---- .../src/containers/Rotations/Rotations.module.css | 2 ++ grafana-plugin/src/containers/Rotations/Rotations.tsx | 4 ++-- .../src/containers/Rotations/ScheduleOverrides.tsx | 4 ++-- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/grafana-plugin/src/containers/Rotation/Rotation.module.css b/grafana-plugin/src/containers/Rotation/Rotation.module.css index ed1d8f1f17..d5fe3e415c 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.module.css +++ b/grafana-plugin/src/containers/Rotation/Rotation.module.css @@ -39,10 +39,6 @@ opacity: 0.5; } -.slots__animate { - /* transition: transform 500ms ease; */ -} - .current-time { position: absolute; left: 450px; diff --git a/grafana-plugin/src/containers/Rotation/Rotation.tsx b/grafana-plugin/src/containers/Rotation/Rotation.tsx index aae75ca627..6f56d6a02c 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.tsx +++ b/grafana-plugin/src/containers/Rotation/Rotation.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo, useState } from 'react'; +import React, { FC, useMemo } from 'react'; import { HorizontalGroup, LoadingPlaceholder } from '@grafana/ui'; import cn from 'classnames/bind'; @@ -68,8 +68,6 @@ export const Rotation: FC = observer((props) => { const days = scheduleViewToDaysInOneRow[scheduleView]; - const [animate, _setAnimate] = useState(true); - const handleRotationClick = (event: React.MouseEvent) => { const rect = event.currentTarget.getBoundingClientRect(); const x = event.clientX - rect.left; //x position within the element. @@ -154,7 +152,7 @@ export const Rotation: FC = observer((props) => { {events ? ( events.length ? (
{events.map((event) => { diff --git a/grafana-plugin/src/containers/Rotations/Rotations.module.css b/grafana-plugin/src/containers/Rotations/Rotations.module.css index ffabb48bc7..d817965f0e 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.module.css +++ b/grafana-plugin/src/containers/Rotations/Rotations.module.css @@ -29,6 +29,8 @@ .layer-title { margin: 8px; + border: var(--border-weak); + background-color: var(--background-canvas); } .rotations-plus-title { diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 3d4a4e575e..564dda82bb 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -159,7 +159,7 @@ class _Rotations extends Component { {layers.map((layer, layerIndex) => (
- + Layer {layer.priority} @@ -200,7 +200,7 @@ class _Rotations extends Component {
- + Layer 1 diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index 6bdd2fcfd6..79ed8cbe94 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -134,7 +134,7 @@ class _ScheduleOverrides extends Component {shiftSwaps && shiftSwaps.length ? ( - + Swaps @@ -158,7 +158,7 @@ class _ScheduleOverrides extends Component ) : null} - + Overrides From e19ccb0eed5159bdf80514900a6def093a80d80e Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Tue, 28 May 2024 14:38:14 +0100 Subject: [PATCH 19/28] fix merging schedules and emotion --- .../containers/Rotation/Rotation.module.css | 4 - .../containers/Rotations/Rotations.styles.ts | 53 ++------ .../src/containers/Rotations/Rotations.tsx | 52 +++++--- .../containers/Rotations/ScheduleFinal.tsx | 30 +++-- .../Rotations/ScheduleOverrides.tsx | 36 ++--- .../containers/Rotations/SchedulePersonal.tsx | 81 ++++++------ .../ScheduleSlot/ScheduleSlot.module.css | 125 ------------------ .../containers/ScheduleSlot/ScheduleSlot.tsx | 6 +- .../src/pages/schedule/Schedule.module.css | 49 ------- .../src/pages/schedule/Schedule.styles.ts | 8 +- .../src/pages/schedule/Schedule.tsx | 3 +- .../src/pages/schedules/Schedules.tsx | 18 +-- 12 files changed, 144 insertions(+), 321 deletions(-) delete mode 100644 grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css delete mode 100644 grafana-plugin/src/pages/schedule/Schedule.module.css diff --git a/grafana-plugin/src/containers/Rotation/Rotation.module.css b/grafana-plugin/src/containers/Rotation/Rotation.module.css index d5fe3e415c..33e8c51069 100644 --- a/grafana-plugin/src/containers/Rotation/Rotation.module.css +++ b/grafana-plugin/src/containers/Rotation/Rotation.module.css @@ -24,10 +24,6 @@ position: relative; } -.root:last-child .timeline { - padding-bottom: 0; -} - .slots { width: 100%; display: flex; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.styles.ts b/grafana-plugin/src/containers/Rotations/Rotations.styles.ts index 1654e45cb9..8805d57c58 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.styles.ts +++ b/grafana-plugin/src/containers/Rotations/Rotations.styles.ts @@ -4,11 +4,16 @@ import { GrafanaTheme2 } from '@grafana/data'; export const getRotationsStyles = (theme: GrafanaTheme2) => { return { root: css` - background: 1px solid ${theme.colors.background.secondary}; - border: ${theme.colors.border.weak}; + background: ${theme.colors.background.secondary}; + border: 1px solid ${theme.colors.border.weak}; display: flex; flex-direction: column; border-radius: ${theme.shape.radius.default}; + + &--withNoBackgroundAndBorder { + background: none; + border: none; + } `, currentTime: css` @@ -18,35 +23,24 @@ export const getRotationsStyles = (theme: GrafanaTheme2) => { top: 0; bottom: 0; z-index: 1; - transition: left 500ms ease; `, header: css` - padding: 0 10px; - `, - - title: css` - margin: 16px 0; + padding: 12px; `, layer: css` display: block; `, - rotations: css` - position: relative; + layerFirst: css` + padding-top: 32px; `, layerTitle: css` - text-align: center; - font-weight: 500; - line-height: 16px; - padding: 8px; - background: ${theme.colors.background.secondary}; - - &:hover { - background: rgba(204, 204, 220, 0.12); - } + margin: 8px; + border: ${theme.colors.border.weak}; + background: ${theme.colors.background.canvas}; `, rotationsPlusTitle: css` @@ -54,25 +48,6 @@ export const getRotationsStyles = (theme: GrafanaTheme2) => { flex-direction: column; `, - headerPlusContent: css` - position: relative; - padding-top: 26px; - padding-bottom: 26px; - `, - - layerHeader: css` - padding: 12px; - display: flex; - justify-content: space-between; - `, - - layerHeaderTitle: css` - font-weight: 400; - font-size: 14px; - line-height: 20px; - color: rgba(204, 204, 220, 0.65); - `, - layerContent: css` position: relative; `, @@ -81,7 +56,7 @@ export const getRotationsStyles = (theme: GrafanaTheme2) => { font-weight: 400; font-size: 12px; line-height: 16px; - text-align: center; + text-align: left; padding: 12px; cursor: pointer; diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 564dda82bb..fb8d5c5015 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -1,8 +1,8 @@ import React, { Component } from 'react'; -import { SelectableValue } from '@grafana/data'; -import { ValuePicker, HorizontalGroup, Button, Tooltip } from '@grafana/ui'; -import cn from 'classnames/bind'; +import { cx } from '@emotion/css'; +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { ValuePicker, HorizontalGroup, Button, Tooltip, withTheme2 } from '@grafana/ui'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; @@ -24,10 +24,9 @@ import { UserActions } from 'utils/authorization/authorization'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; import { findColor } from './Rotations.helpers'; +import { getRotationsStyles } from './Rotations.styles'; -import styles from './Rotations.module.css'; - -const cx = cn.bind(styles); +import animationStyles from './Rotations.module.css'; interface RotationsProps extends WithStoreProps { shiftIdToShowRotationForm?: Shift['id'] | 'new'; @@ -44,6 +43,7 @@ interface RotationsProps extends WithStoreProps { disabled: boolean; filters: ScheduleFiltersType; onSlotClick?: (event: Event) => void; + theme: GrafanaTheme2; } interface RotationsState { @@ -71,6 +71,7 @@ class _Rotations extends Component { onShowShiftSwapForm, onSlotClick, layerPriorityToShowRotationForm, + theme, } = this.props; const { shiftStartToShowRotationForm, shiftEndToShowRotationForm } = this.state; @@ -99,10 +100,12 @@ class _Rotations extends Component { const isTypeReadOnly = schedule && (schedule?.type === ScheduleType.Ical || schedule?.type === ScheduleType.Calendar); + const styles = getRotationsStyles(theme); + return ( <> -
-
+
+
Rotations @@ -144,33 +147,40 @@ class _Rotations extends Component {
-
+
{layers && layers.length ? ( {!currentTimeHidden && (
)} {layers.map((layer, layerIndex) => ( - -
- + +
+ Layer {layer.priority} -
+
{layer.shifts.map(({ shiftId, isPreview, events }, rotationIndex) => ( { @@ -198,14 +208,14 @@ class _Rotations extends Component { ) : (
-
-
- +
+
+ Layer 1 -
+
{ this.handleAddLayer(nextPriority, shiftStart, shiftEnd); @@ -220,7 +230,7 @@ class _Rotations extends Component { )} {nextPriority > 1 && (
{ if (disabled) { return; @@ -334,4 +344,4 @@ class _Rotations extends Component { }; } -export const Rotations = withMobXProviderContext(_Rotations); +export const Rotations = withMobXProviderContext(withTheme2(_Rotations)); diff --git a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx index 73b478bb5e..522a76c355 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx @@ -1,10 +1,12 @@ import React, { FC, useMemo } from 'react'; -import { HorizontalGroup } from '@grafana/ui'; -import cn from 'classnames/bind'; +import { cx } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; +import { HorizontalGroup, useStyles2, withTheme2 } from '@grafana/ui'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import { bem } from 'styles/utils.styles'; import { ScheduleFiltersType } from 'components/ScheduleFilters/ScheduleFilters.types'; import { Text } from 'components/Text/Text'; @@ -25,10 +27,9 @@ import { withMobXProviderContext } from 'state/withStore'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; import { findColor } from './Rotations.helpers'; +import { getRotationsStyles } from './Rotations.styles'; -import styles from './Rotations.module.css'; - -const cx = cn.bind(styles); +import animationStyles from './Rotations.module.css'; interface ScheduleFinalProps extends WithStoreProps { scheduleId: Schedule['id']; @@ -39,6 +40,7 @@ interface ScheduleFinalProps extends WithStoreProps { filters: ScheduleFiltersType; onSlotClick?: (event: Event) => void; scheduleView?: ScheduleView; + theme: GrafanaTheme2; } const _ScheduleFinal: FC = observer( @@ -57,6 +59,8 @@ const _ScheduleFinal: FC = observer( scheduleStore: { scheduleView: storeScheduleView }, } = store; + const styles = useStyles2(getRotationsStyles); + const scheduleView = propsScheduleView || storeScheduleView; const shifts = flattenShiftEvents(getShiftsFromStore(store, scheduleId, calendarStartDate)); @@ -83,9 +87,9 @@ const _ScheduleFinal: FC = observer( }, [calendarStartDate, scheduleView]); return ( -
+
{!simplified && ( -
+
Final schedule @@ -93,16 +97,16 @@ const _ScheduleFinal: FC = observer(
)} -
+
{rows.map(({ startDate }, index) => ( - +
= observer( {shifts?.length ? ( shifts.map(({ events }, index) => { return ( - + = observer( ); }) ) : ( - + )} @@ -146,4 +150,4 @@ const _ScheduleFinal: FC = observer( } ); -export const ScheduleFinal = withMobXProviderContext(_ScheduleFinal); +export const ScheduleFinal = withMobXProviderContext(withTheme2(_ScheduleFinal)); diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index 79ed8cbe94..4e2061406d 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -1,7 +1,8 @@ import React, { Component } from 'react'; -import { Button, HorizontalGroup, Tooltip } from '@grafana/ui'; -import cn from 'classnames/bind'; +import { cx } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Button, HorizontalGroup, Tooltip, withTheme2 } from '@grafana/ui'; import dayjs from 'dayjs'; import { observer } from 'mobx-react'; import { CSSTransition, TransitionGroup } from 'react-transition-group'; @@ -28,10 +29,9 @@ import { UserActions } from 'utils/authorization/authorization'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; import { findColor } from './Rotations.helpers'; +import { getRotationsStyles } from './Rotations.styles'; -import styles from './Rotations.module.css'; - -const cx = cn.bind(styles); +import animationStyles from './Rotations.module.css'; interface ScheduleOverridesProps extends WithStoreProps { shiftStartToShowOverrideForm: dayjs.Dayjs; @@ -46,6 +46,7 @@ interface ScheduleOverridesProps extends WithStoreProps { disabled: boolean; disableShiftSwaps: boolean; filters: ScheduleFiltersType; + theme: GrafanaTheme2; } interface ScheduleOverridesState { @@ -74,6 +75,7 @@ class _ScheduleOverrides extends Component -
-
+
+
Overrides and swaps @@ -130,17 +134,17 @@ class _ScheduleOverrides extends Component
- {!currentTimeHidden &&
} + {!currentTimeHidden &&
} {shiftSwaps && shiftSwaps.length ? ( - - + + Swaps {shiftSwaps.map(({ isPreview, events }, index) => ( - + ) : null} - - + + Overrides {shifts && shifts.length ? ( shifts.map(({ shiftId, isPreview, events }, index) => ( - + )) ) : ( - + void; + theme: GrafanaTheme2; } const _SchedulePersonal: FC = observer(({ userPk, onSlotClick, history }) => { @@ -94,52 +95,52 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC const emptyRotationsText = updatePersonalEventsLoading ? 'Loading ...' : 'There are no schedules relevant to user'; + const styles = useStyles2(getRotationsStyles); + return ( -
-
-
- +
+
+ + + + On-call schedule {storeUser.username} + + {isOncall ? ( + + ) : ( + /* @ts-ignore */ + + )} + + - On-call schedule {storeUser.username} + {timezoneStore.calendarStartDate.format('DD MMM')} -{' '} + {timezoneStore.calendarStartDate.add(6, 'day').format('DD MMM')} - {isOncall ? ( - - ) : ( - /* @ts-ignore */ - - )} - - - - - {timezoneStore.calendarStartDate.format('DD MMM')} -{' '} - {timezoneStore.calendarStartDate.add(6, 'day').format('DD MMM')} - - + + + - - - - -
+
-
- {!currentTimeHidden &&
} +
+ {!currentTimeHidden &&
} - + {shifts?.length ? ( shifts.map(({ events }, index) => { return ( - + = observer(({ userPk, onSlotC ); }) ) : ( - + )} @@ -164,4 +165,4 @@ const _SchedulePersonal: FC = observer(({ userPk, onSlotC ); }); -export const SchedulePersonal = withRouter(_SchedulePersonal); +export const SchedulePersonal = withRouter(withTheme2(_SchedulePersonal)); diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css deleted file mode 100644 index df1d8b15f4..0000000000 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.module.css +++ /dev/null @@ -1,125 +0,0 @@ -.root { - height: 28px; - background: #595959; - border-radius: 2px; - position: relative; - display: flex; - overflow: hidden; - margin-right: 1px; - padding: 4px; - align-items: center; - transition: opacity 0.2s ease; - cursor: pointer; -} - -.working-hours { - position: absolute; - top: 0; - left: 0; - pointer-events: none; -} - -.stack { - display: flex; - flex-direction: column; - gap: 1px; - flex-shrink: 0; -} - -.root__type_gap { - background: rgba(209, 14, 92, 0.2); - border: 1px dashed #ff5286; - color: rgba(209, 14, 92, 0.5); - visibility: hidden; -} - -.root__type_shift-swap { - background: rgba(255, 153, 0, 0.18); -} - -.no-user { - width: 12px; - height: 12px; - background: var(--tag-background-primary); - border-radius: 50%; - display: flex; - justify-content: center; -} - -.root__inactive { - opacity: 0.3; -} - -.title { - z-index: 1; - font-size: 12px; - width: 100%; - font-weight: 500; - white-space: nowrap; -} - -.label { - background: rgba(255, 255, 255, 0.7); - border-radius: 2px; - display: inline-block; - padding: 2px 4px; - line-height: 16px; - z-index: 1; - font-size: 10px; - font-weight: bold; - margin-right: 5px; - flex-shrink: 0; -} - -.details { - width: 300px; - padding: 5px 0; -} - -.details-user-status { - width: 10px; - height: 10px; - border-radius: 50%; -} - -.details-user-status__type_success { - background-color: var(--success-text-color); -} - -.time { - position: absolute; - top: 0; - bottom: 0; - width: 1px; - background-color: white; - z-index: 2; -} - -.is-oncall-icon { - color: var(--oncall-icon-stroke-color); - vertical-align: middle; -} - -.details-icon { - width: 16px; - margin-right: 4px; -} - -.badge { - width: 8px; - height: 8px; - border-radius: 50%; - margin: 0 auto; -} - -.username { - word-break: break-word; -} - -.second-column { - width: 120px; -} - -.icon { - color: var(--secondary-text-color); -} diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx index ba51f05825..617fcf9cb6 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx @@ -535,7 +535,7 @@ const getStyles = (theme: GrafanaTheme2) => { position: relative; display: flex; overflow: hidden; - margin: 0 1px; + margin-right: 1px; padding: 4px; align-items: center; transition: opacity 0.2s ease; @@ -566,9 +566,7 @@ const getStyles = (theme: GrafanaTheme2) => { // TODO: Same here swap: css` - border-radius: 10px; - background: #ff99002e; - height: 20px; + background: rgba(255, 153, 0, 0.18); `, noUser: css` diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css deleted file mode 100644 index eb21e80392..0000000000 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ /dev/null @@ -1,49 +0,0 @@ -.root { - --rotations-border: var(--border-weak); - --rotations-background: var(--background-secondary); -} - -.title { - display: flex; - flex-wrap: wrap; - flex-direction: row; - column-gap: 8px; - row-gap: 8px; - min-width: 250px; - align-items: center; -} - -.header { - position: sticky; /* TODO check */ - width: 100%; -} - -.desc { - width: 736px; -} - -.users-timezones { - width: 100%; -} - -.controls { - width: 100%; - position: sticky; - top: 0; - z-index: 2; - padding: 16px 0; - background: var(--background-primary); -} - -.rotations { - display: flex; - flex-direction: column; - gap: 16px; - position: relative; - width: 100%; -} - -.not-found { - margin: 50px auto; - text-align: center; -} diff --git a/grafana-plugin/src/pages/schedule/Schedule.styles.ts b/grafana-plugin/src/pages/schedule/Schedule.styles.ts index 8c4bc0bd61..077d1ac22b 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.styles.ts +++ b/grafana-plugin/src/pages/schedule/Schedule.styles.ts @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; -export const getScheduleStyles = () => { +export const getScheduleStyles = (theme: GrafanaTheme2) => { return { title: css` display: flex; @@ -27,6 +28,11 @@ export const getScheduleStyles = () => { controls: css` width: 100%; + position: sticky; + top: 0; + z-index: 2; + padding: 16px 0; + background-color: ${theme.colors.background.primary}; `, rotations: css` diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index e192827c5a..3c80a09278 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -120,6 +120,7 @@ class _SchedulePage extends React.Component { const styles = getSchedulesStyles(); -
- -
; + return ( +
+ +
+ ); }; getScheduleClickHandler = (scheduleId: Schedule['id']) => { From 0da6a78ec4d158c059b0891c6b4e1a8bcc598956 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Tue, 28 May 2024 14:40:28 +0100 Subject: [PATCH 20/28] Update grafana-plugin/src/pages/schedule/Schedule.tsx Co-authored-by: Dominik Broj --- grafana-plugin/src/pages/schedule/Schedule.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 3c80a09278..383229068d 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -167,7 +167,7 @@ class _SchedulePage extends React.Component Date: Tue, 28 May 2024 15:53:41 +0100 Subject: [PATCH 21/28] add HTML_ID const to locate html elements --- .../containers/RotationForm/ScheduleOverrideForm.tsx | 4 ++-- grafana-plugin/src/containers/Rotations/Rotations.tsx | 2 +- .../src/containers/Rotations/ScheduleOverrides.tsx | 2 +- grafana-plugin/src/pages/schedule/Schedule.tsx | 10 +++++----- grafana-plugin/src/utils/DOM.ts | 7 ++++++- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx index d28e510a2a..24659b1690 100644 --- a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx @@ -15,7 +15,7 @@ import { Schedule, Shift } from 'models/schedule/schedule.types'; import { ApiSchemas } from 'network/oncall-api/api.types'; import { getDateTime, getUTCString } from 'pages/schedule/Schedule.helpers'; import { useStore } from 'state/useStore'; -import { getCoords, waitForElement } from 'utils/DOM'; +import { HTML_ID, getCoords, waitForElement } from 'utils/DOM'; import { GRAFANA_HEADER_HEIGHT } from 'utils/consts'; import { useDebouncedCallback } from 'utils/hooks'; @@ -79,7 +79,7 @@ export const ScheduleOverrideForm: FC = (props) => { useEffect(() => { (async () => { if (isOpen) { - const elm = await waitForElement('#overrides-list'); + const elm = await waitForElement(`#${HTML_ID.OVERRIDES_AND_SWAPS}`); const modal = document.querySelector(`.${cx('draggable')}`) as HTMLDivElement; const coords = getCoords(elm); const offsetTop = Math.min( diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index fb8d5c5015..6eb354c139 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -104,7 +104,7 @@ class _Rotations extends Component { return ( <> -
+
diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index 4e2061406d..ea71686873 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -99,7 +99,7 @@ class _ScheduleOverrides extends Component -
+
diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 3c80a09278..713afb004e 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -42,7 +42,7 @@ import { Event, Layer, Schedule, ScheduleType, ScheduleView, Shift, ShiftSwap } import { UserHelper } from 'models/user/user.helpers'; import { PageProps, WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; -import { scrollToElement } from 'utils/DOM'; +import { HTML_ID, scrollToElement } from 'utils/DOM'; import { isUserActionAllowed, UserActions } from 'utils/authorization/authorization'; import { PLUGIN_ROOT } from 'utils/consts'; @@ -236,7 +236,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById('rotations')); + scrollToElement(document.getElementById(HTML_ID.ROTATIONS)); this.handleShowRotationForm('new', layer.priority); }} @@ -245,7 +245,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById('rotations')); + scrollToElement(document.getElementById(HTML_ID.ROTATIONS)); this.handleShowRotationForm('new', nextPriority); }} @@ -253,7 +253,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById('overrides-list')); + scrollToElement(document.getElementById(HTML_ID.OVERRIDES_AND_SWAPS)); this.handleShowShiftSwapForm('new'); }} @@ -261,7 +261,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById('overrides-list')); + scrollToElement(document.getElementById(HTML_ID.OVERRIDES_AND_SWAPS)); this.handleShowOverridesForm('new'); }} diff --git a/grafana-plugin/src/utils/DOM.ts b/grafana-plugin/src/utils/DOM.ts index fcf08d8eff..56e5c902c5 100644 --- a/grafana-plugin/src/utils/DOM.ts +++ b/grafana-plugin/src/utils/DOM.ts @@ -19,7 +19,7 @@ export const waitForElement = (selector: string) => { }; export const scrollToElement = (element: Element, behavior: ScrollBehavior = 'instant' as ScrollBehavior) => { - document.getElementById('rotations').scrollIntoView({ behavior, block: 'center' }); + element.scrollIntoView({ behavior, block: 'center' }); }; export const getCoords = (elem) => { @@ -40,3 +40,8 @@ export const getCoords = (elem) => { return { top: Math.round(top), left: Math.round(left) }; }; + +export const HTML_ID = { + ROTATIONS: 'oncall-rotations', + OVERRIDES_AND_SWAPS: 'oncall-overrides-and-swaps', +} as const; From 892b9402aa350591a930cb638bee0c3be96e1b56 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Wed, 29 May 2024 14:45:41 +0100 Subject: [PATCH 22/28] fix shift swaps styling --- .../containers/Rotations/Rotations.styles.ts | 2 +- .../Rotations/ScheduleOverrides.tsx | 14 ++-- .../ScheduleSlot/ScheduleSlot.helpers.ts | 19 +++++ .../containers/ScheduleSlot/ScheduleSlot.tsx | 73 +++++++++---------- .../src/pages/schedule/Schedule.module.css | 49 +++++++++++++ .../src/pages/schedule/Schedule.tsx | 2 + .../src/pages/schedules/Schedules.styles.ts | 8 +- .../src/pages/schedules/Schedules.tsx | 1 + grafana-plugin/src/utils/string.ts | 2 +- 9 files changed, 125 insertions(+), 45 deletions(-) create mode 100644 grafana-plugin/src/pages/schedule/Schedule.module.css diff --git a/grafana-plugin/src/containers/Rotations/Rotations.styles.ts b/grafana-plugin/src/containers/Rotations/Rotations.styles.ts index 8805d57c58..c0f9104679 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.styles.ts +++ b/grafana-plugin/src/containers/Rotations/Rotations.styles.ts @@ -19,7 +19,7 @@ export const getRotationsStyles = (theme: GrafanaTheme2) => { currentTime: css` position: absolute; width: 1px; - background: ${theme.colors.gradients.brandVertical} + background: ${theme.colors.gradients.brandVertical}; top: 0; bottom: 0; z-index: 1; diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index ea71686873..288920d22d 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -162,11 +162,15 @@ class _ScheduleOverrides extends Component ) : null} - - - Overrides - - + {shifts && shifts.length ? ( + + + + Overrides + + + + ) : null} {shifts && shifts.length ? ( shifts.map(({ shiftId, isPreview, events }, index) => ( diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.helpers.ts b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.helpers.ts index ab26807eaa..b66acdeeaa 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.helpers.ts +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.helpers.ts @@ -1,5 +1,24 @@ +import { SHIFT_SWAP_COLOR } from 'models/schedule/schedule.helpers'; import { ApiSchemas } from 'network/oncall-api/api.types'; export const getTitle = (user: ApiSchemas['User']) => { return user ? user.username.split(' ')[0] : null; }; + +export const getScheduleSlotStyleParams = (baseColor: string, isShiftSwap: boolean, hasBenefactor: boolean) => { + let color = baseColor; + let backgroundColor = color; + let border = undefined; + let textColor = '#fff'; + if (isShiftSwap) { + backgroundColor = SHIFT_SWAP_COLOR; + if (!hasBenefactor) { + color = SHIFT_SWAP_COLOR; + backgroundColor = 'transparent'; + border = `1px solid ${SHIFT_SWAP_COLOR}`; + textColor = SHIFT_SWAP_COLOR; + } + } + + return { color, backgroundColor, border, textColor }; +}; diff --git a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx index 617fcf9cb6..81161a8372 100644 --- a/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx +++ b/grafana-plugin/src/containers/ScheduleSlot/ScheduleSlot.tsx @@ -7,7 +7,6 @@ import dayjs from 'dayjs'; import { observer } from 'mobx-react'; import { COLORS, getLabelCss } from 'styles/utils.styles'; -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'; @@ -18,8 +17,9 @@ import { Event, ScheduleView, ShiftSwap } from 'models/schedule/schedule.types'; import { getTzOffsetString } from 'models/timezone/timezone.helpers'; import { ApiSchemas } from 'network/oncall-api/api.types'; import { useStore } from 'state/useStore'; +import { truncateTitle } from 'utils/string'; -import { getTitle } from './ScheduleSlot.helpers'; +import { getScheduleSlotStyleParams, getTitle } from './ScheduleSlot.helpers'; interface ScheduleSlotProps { event: Event; @@ -165,21 +165,26 @@ const ShiftSwapEvent = (props: ShiftSwapEventProps) => { const beneficiaryStoreUser = store.userStore.items[shiftSwap?.beneficiary?.pk]; const benefactorStoreUser = store.userStore.items[shiftSwap?.benefactor?.pk]; + const { backgroundColor, border, textColor } = getScheduleSlotStyleParams( + SHIFT_SWAP_COLOR, + true, + Boolean(shiftSwap?.benefactor) + ); + const scheduleSlotContent = ( -
+
{shiftSwap && ( - - {beneficiary && } - {benefactor ? ( - - ) : ( -
- - ? - -
- )} -
+
+ {truncateTitle(beneficiary.display_name, 9)} → {benefactor ? truncateTitle(benefactor.display_name, 9) : '?'} +
)}
); @@ -228,7 +233,7 @@ const RegularEvent = (props: RegularEventProps) => { event, onShiftSwapClick, filters, - color, + color: propsColor, start, duration, handleAddOverride, @@ -267,19 +272,17 @@ const RegularEvent = (props: RegularEventProps) => { const isShiftSwap = Boolean(swap_request); - const title = isShiftSwap ? 'Shift swap' : showScheduleNameAsSlotTitle ? schedule?.name : getShiftName(shift); - - let backgroundColor = color; - let border = undefined; - let textColor = '#fff'; - if (isShiftSwap) { - backgroundColor = SHIFT_SWAP_COLOR; - if (!swap_request.user) { - backgroundColor = 'transparent'; - border = `1px solid ${SHIFT_SWAP_COLOR}`; - textColor = SHIFT_SWAP_COLOR; - } - } + const title = isShiftSwap + ? `Shift swap to ${getShiftName(shift)}` + : showScheduleNameAsSlotTitle + ? schedule?.name + : getShiftName(shift); + + const { color, backgroundColor, border, textColor } = getScheduleSlotStyleParams( + propsColor, + Boolean(swap_request), + Boolean(swap_request?.user) + ); const scheduleSlotContent = (
{ duration={duration} /> )} -
{swap_request && !swap_request.user ? userTitle + ' → ?' : userTitle}
+
+ {swap_request && !swap_request.user ? truncateTitle(userTitle, 9) + ' → ?' : userTitle} +
); @@ -334,7 +339,7 @@ const RegularEvent = (props: RegularEventProps) => { : handleAddShiftSwap } handleOpenSchedule={handleOpenSchedule} - color={backgroundColor} + color={color} currentMoment={currentMoment} /> } @@ -564,11 +569,6 @@ const getStyles = (theme: GrafanaTheme2) => { visibility: hidden; `, - // TODO: Same here - swap: css` - background: rgba(255, 153, 0, 0.18); - `, - noUser: css` width: 12px; height: 12px; @@ -584,7 +584,6 @@ const getStyles = (theme: GrafanaTheme2) => { title: css` z-index: 1; - color: #fff; font-size: 12px; width: 100%; font-weight: 500; diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css new file mode 100644 index 0000000000..eb21e80392 --- /dev/null +++ b/grafana-plugin/src/pages/schedule/Schedule.module.css @@ -0,0 +1,49 @@ +.root { + --rotations-border: var(--border-weak); + --rotations-background: var(--background-secondary); +} + +.title { + display: flex; + flex-wrap: wrap; + flex-direction: row; + column-gap: 8px; + row-gap: 8px; + min-width: 250px; + align-items: center; +} + +.header { + position: sticky; /* TODO check */ + width: 100%; +} + +.desc { + width: 736px; +} + +.users-timezones { + width: 100%; +} + +.controls { + width: 100%; + position: sticky; + top: 0; + z-index: 2; + padding: 16px 0; + background: var(--background-primary); +} + +.rotations { + display: flex; + flex-direction: column; + gap: 16px; + position: relative; + width: 100%; +} + +.not-found { + margin: 50px auto; + text-align: center; +} diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index cc2d80dbf6..8478488fbe 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -111,6 +111,7 @@ class _SchedulePage extends React.Component this.setState({ calendarStartDatePickerIsOpen: false })} /> diff --git a/grafana-plugin/src/pages/schedules/Schedules.styles.ts b/grafana-plugin/src/pages/schedules/Schedules.styles.ts index 7bbc2947f6..b2c9390650 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.styles.ts +++ b/grafana-plugin/src/pages/schedules/Schedules.styles.ts @@ -4,7 +4,13 @@ export const getSchedulesStyles = () => { return { schedule: css` position: relative; - margin: 20px 0; + `, + + table: css` + td { + padding-top: 5px; + padding-bottom: 5px; + } `, schedulePersonal: css` diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 8443febee9..45e77c57ed 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -107,6 +107,7 @@ class _SchedulesPage extends React.Component
Date: Wed, 29 May 2024 18:10:11 +0100 Subject: [PATCH 23/28] add e2e test --- .../e2e-tests/schedules/scheduleView.test.ts | 37 +++++++++++++++++++ .../src/containers/Rotations/Rotations.tsx | 3 +- .../containers/Rotations/ScheduleFinal.tsx | 6 ++- .../Rotations/ScheduleOverrides.tsx | 3 +- .../TimelineMarks/TimelineMarks.tsx | 6 ++- .../src/pages/schedule/Schedule.tsx | 9 +++-- grafana-plugin/src/utils/DOM.ts | 5 ++- 7 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 grafana-plugin/e2e-tests/schedules/scheduleView.test.ts diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts new file mode 100644 index 0000000000..e673340a04 --- /dev/null +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -0,0 +1,37 @@ +import { scheduleViewToDaysInOneRow } from 'models/schedule/schedule.helpers'; +import { ScheduleView } from 'models/schedule/schedule.types'; +import { HTML_ID } from 'utils/DOM'; + +import { expect, test } from '../fixtures'; +import { generateRandomValue } from '../utils/forms'; +import { createOnCallScheduleWithRotation } from '../utils/schedule'; + +test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { + const { page, userName } = adminRolePage; + + const onCallScheduleName = generateRandomValue(); + await createOnCallScheduleWithRotation(page, onCallScheduleName, userName); + + // ScheduleView.OneWeek is selected by default + expect(await page.getByLabel(ScheduleView.OneWeek, { exact: true }).isChecked()).toBe(true); + + const count = await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count(); + expect(count).toStrictEqual(scheduleViewToDaysInOneRow[ScheduleView.OneWeek]); + + /* + for some reason loop isn't working + [ScheduleView.TwoWeeks, ScheduleView.OneMonth, ScheduleView.OneWeek].forEach(async (scheduleView) => { + await page.locator('.scheduleViewToogler').getByLabel(scheduleView, { exact: true }).click(); + expect(await page.getByLabel(scheduleView, { exact: true }).isChecked()).toBe(true); + }); */ + + await page.locator('.scheduleViewToogler').getByLabel(ScheduleView.TwoWeeks, { exact: true }).click(); + expect(await page.getByLabel(ScheduleView.TwoWeeks, { exact: true }).isChecked()).toBe(true); + expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toStrictEqual( + scheduleViewToDaysInOneRow[ScheduleView.TwoWeeks] + ); + + await page.locator('.scheduleViewToogler').getByLabel(ScheduleView.OneMonth, { exact: true }).click(); + expect(await page.getByLabel(ScheduleView.OneMonth, { exact: true }).isChecked()).toBe(true); + expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toBeGreaterThanOrEqual(28); +}); diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 6eb354c139..5ba47b9243 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -20,6 +20,7 @@ import { ApiSchemas } from 'network/oncall-api/api.types'; import { getCurrentTimeX } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; +import { HTML_ID } from 'utils/DOM'; import { UserActions } from 'utils/authorization/authorization'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; @@ -104,7 +105,7 @@ class _Rotations extends Component { return ( <> -
+
diff --git a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx index 522a76c355..723f217bac 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleFinal.tsx @@ -24,6 +24,7 @@ import { Event, Schedule, ScheduleView, ShiftSwap } from 'models/schedule/schedu import { getCurrentTimeX } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; +import { HTML_ID } from 'utils/DOM'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; import { findColor } from './Rotations.helpers'; @@ -87,7 +88,10 @@ const _ScheduleFinal: FC = observer( }, [calendarStartDate, scheduleView]); return ( -
+
{!simplified && (
diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index 288920d22d..261aeba731 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -25,6 +25,7 @@ import { Schedule, Shift, ShiftEvents, ShiftSwap } from 'models/schedule/schedul import { getCurrentTimeX } from 'pages/schedule/Schedule.helpers'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; +import { HTML_ID } from 'utils/DOM'; import { UserActions } from 'utils/authorization/authorization'; import { DEFAULT_TRANSITION_TIMEOUT } from './Rotations.config'; @@ -99,7 +100,7 @@ class _ScheduleOverrides extends Component -
+
diff --git a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx index 605df70941..9685b66762 100644 --- a/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/containers/TimelineMarks/TimelineMarks.tsx @@ -82,7 +82,11 @@ export const TimelineMarks: FC = observer((props) => { const isWeekend = m.moment.day() === 0 || m.moment.day() === 6; return ( -
+
{m.moment.date() === 1 ? m.moment.format('ddd D MMM') : m.moment.format('ddd D')} diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 8478488fbe..1e6708630f 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -237,7 +237,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById(HTML_ID.ROTATIONS)); + scrollToElement(document.getElementById(HTML_ID.SCHEDULE_ROTATIONS)); this.handleShowRotationForm('new', layer.priority); }} @@ -246,7 +246,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById(HTML_ID.ROTATIONS)); + scrollToElement(document.getElementById(HTML_ID.SCHEDULE_ROTATIONS)); this.handleShowRotationForm('new', nextPriority); }} @@ -254,7 +254,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById(HTML_ID.OVERRIDES_AND_SWAPS)); + scrollToElement(document.getElementById(HTML_ID.SCHEDULE_OVERRIDES_AND_SWAPS)); this.handleShowShiftSwapForm('new'); }} @@ -262,7 +262,7 @@ class _SchedulePage extends React.Component { - scrollToElement(document.getElementById(HTML_ID.OVERRIDES_AND_SWAPS)); + scrollToElement(document.getElementById(HTML_ID.SCHEDULE_OVERRIDES_AND_SWAPS)); this.handleShowOverridesForm('new'); }} @@ -348,6 +348,7 @@ class _SchedulePage extends React.Component { }; export const HTML_ID = { - ROTATIONS: 'oncall-rotations', - OVERRIDES_AND_SWAPS: 'oncall-overrides-and-swaps', + SCHEDULE_FINAL: 'oncall-schedule-final', + SCHEDULE_ROTATIONS: 'oncall-schedule-rotations', + SCHEDULE_OVERRIDES_AND_SWAPS: 'oncall-schedule-overrides-and-swaps', } as const; From 962c3e371c372fa30c79218a78c38b82ad784e27 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Wed, 29 May 2024 22:47:29 +0100 Subject: [PATCH 24/28] add unit tests --- .../e2e-tests/schedules/scheduleView.test.ts | 10 ++--- .../models/schedule/schedule.helpers.test.ts | 26 +++++++++++ .../pages/schedule/Schedule.helpers.test.ts | 45 +++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 grafana-plugin/src/models/schedule/schedule.helpers.test.ts create mode 100644 grafana-plugin/src/pages/schedule/Schedule.helpers.test.ts diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts index e673340a04..efd3b3505e 100644 --- a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -15,12 +15,12 @@ test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage // ScheduleView.OneWeek is selected by default expect(await page.getByLabel(ScheduleView.OneWeek, { exact: true }).isChecked()).toBe(true); - const count = await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count(); - expect(count).toStrictEqual(scheduleViewToDaysInOneRow[ScheduleView.OneWeek]); + expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toStrictEqual( + scheduleViewToDaysInOneRow[ScheduleView.OneWeek] + ); - /* - for some reason loop isn't working - [ScheduleView.TwoWeeks, ScheduleView.OneMonth, ScheduleView.OneWeek].forEach(async (scheduleView) => { + // for some reason loop isn't working + /* [ScheduleView.TwoWeeks, ScheduleView.OneMonth, ScheduleView.OneWeek].forEach(async (scheduleView) => { await page.locator('.scheduleViewToogler').getByLabel(scheduleView, { exact: true }).click(); expect(await page.getByLabel(scheduleView, { exact: true }).isChecked()).toBe(true); }); */ diff --git a/grafana-plugin/src/models/schedule/schedule.helpers.test.ts b/grafana-plugin/src/models/schedule/schedule.helpers.test.ts new file mode 100644 index 0000000000..e6f2b22760 --- /dev/null +++ b/grafana-plugin/src/models/schedule/schedule.helpers.test.ts @@ -0,0 +1,26 @@ +import dayjs from 'dayjs'; + +import { getTotalDaysToDisplay } from './schedule.helpers'; +import { ScheduleView } from './schedule.types'; + +describe('getTotalDaysToDisplay', () => { + const date = dayjs('2024-05-27T00:00:00Z').utc(); + + it(`should return correct total days to display in final schedule if scheduleView=Week`, () => { + const result = getTotalDaysToDisplay(ScheduleView.OneWeek, date); + + expect(result).toBe(7); + }); + + it(`should return correcnt total days to display in final schedule if scheduleView=2 weeks`, () => { + const result = getTotalDaysToDisplay(ScheduleView.TwoWeeks, date); + + expect(result).toBe(14); + }); + + it(`should return correcnt total days to display in final schedule if scheduleView=Month`, () => { + const result = getTotalDaysToDisplay(ScheduleView.OneMonth, date); + + expect(result).toBe(35); + }); +}); diff --git a/grafana-plugin/src/pages/schedule/Schedule.helpers.test.ts b/grafana-plugin/src/pages/schedule/Schedule.helpers.test.ts new file mode 100644 index 0000000000..0dc686fae4 --- /dev/null +++ b/grafana-plugin/src/pages/schedule/Schedule.helpers.test.ts @@ -0,0 +1,45 @@ +import dayjs from 'dayjs'; + +import { ScheduleView } from 'models/schedule/schedule.types'; + +import { getNewCalendarStartDate } from './Schedule.helpers'; + +describe('getNewCalendarStartDate', () => { + const date = dayjs('2024-05-27T00:00:00Z').utc(); + + it(`should return correct next calendar date if scheduleView=Week`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.OneWeek, 'next'); + + expect(result.toString()).toBe('Mon, 03 Jun 2024 00:00:00 GMT'); + }); + + it(`should return correct previous calendar date if scheduleView=Week`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.OneWeek, 'prev'); + + expect(result.toString()).toBe('Mon, 20 May 2024 00:00:00 GMT'); + }); + + it(`should return correct next calendar date if scheduleView=2 weeks`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.TwoWeeks, 'next'); + + expect(result.toString()).toBe('Mon, 10 Jun 2024 00:00:00 GMT'); + }); + + it(`should return correct previous calendar date if scheduleView=2 weeks`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.TwoWeeks, 'prev'); + + expect(result.toString()).toBe('Mon, 13 May 2024 00:00:00 GMT'); + }); + + it(`should return correct next calendar date if scheduleView=Month`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.OneMonth, 'next'); + + expect(result.toString()).toBe('Mon, 01 Jul 2024 00:00:00 GMT'); + }); + + it(`should return correct previous calendar date if scheduleView=Month`, () => { + const result = getNewCalendarStartDate(date, ScheduleView.OneMonth, 'prev'); + + expect(result.toString()).toBe('Mon, 29 Apr 2024 00:00:00 GMT'); + }); +}); From f0104b5a5314a2350da6e71c629367d37a8995e2 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 30 May 2024 09:49:32 +0100 Subject: [PATCH 25/28] polish tests, remove Schedule.module.css --- .../e2e-tests/schedules/scheduleView.test.ts | 10 +--- .../src/pages/schedule/Schedule.module.css | 49 ------------------- .../src/pages/schedule/Schedule.tsx | 1 - 3 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 grafana-plugin/src/pages/schedule/Schedule.module.css diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts index efd3b3505e..7d67a772b0 100644 --- a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -19,19 +19,13 @@ test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage scheduleViewToDaysInOneRow[ScheduleView.OneWeek] ); - // for some reason loop isn't working - /* [ScheduleView.TwoWeeks, ScheduleView.OneMonth, ScheduleView.OneWeek].forEach(async (scheduleView) => { - await page.locator('.scheduleViewToogler').getByLabel(scheduleView, { exact: true }).click(); - expect(await page.getByLabel(scheduleView, { exact: true }).isChecked()).toBe(true); - }); */ - - await page.locator('.scheduleViewToogler').getByLabel(ScheduleView.TwoWeeks, { exact: true }).click(); + await page.getByLabel(ScheduleView.TwoWeeks, { exact: true }).click(); expect(await page.getByLabel(ScheduleView.TwoWeeks, { exact: true }).isChecked()).toBe(true); expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toStrictEqual( scheduleViewToDaysInOneRow[ScheduleView.TwoWeeks] ); - await page.locator('.scheduleViewToogler').getByLabel(ScheduleView.OneMonth, { exact: true }).click(); + await page.getByLabel(ScheduleView.OneMonth, { exact: true }).click(); expect(await page.getByLabel(ScheduleView.OneMonth, { exact: true }).isChecked()).toBe(true); expect(await page.locator(`#${HTML_ID.SCHEDULE_FINAL} .TEST_weekday`).count()).toBeGreaterThanOrEqual(28); }); diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css deleted file mode 100644 index eb21e80392..0000000000 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ /dev/null @@ -1,49 +0,0 @@ -.root { - --rotations-border: var(--border-weak); - --rotations-background: var(--background-secondary); -} - -.title { - display: flex; - flex-wrap: wrap; - flex-direction: row; - column-gap: 8px; - row-gap: 8px; - min-width: 250px; - align-items: center; -} - -.header { - position: sticky; /* TODO check */ - width: 100%; -} - -.desc { - width: 736px; -} - -.users-timezones { - width: 100%; -} - -.controls { - width: 100%; - position: sticky; - top: 0; - z-index: 2; - padding: 16px 0; - background: var(--background-primary); -} - -.rotations { - display: flex; - flex-direction: column; - gap: 16px; - position: relative; - width: 100%; -} - -.not-found { - margin: 50px auto; - text-align: center; -} diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 1e6708630f..ff8625d6a9 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -348,7 +348,6 @@ class _SchedulePage extends React.Component Date: Thu, 30 May 2024 11:11:33 +0100 Subject: [PATCH 26/28] update personal events on schedule deletion --- grafana-plugin/src/pages/schedules/Schedules.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grafana-plugin/src/pages/schedules/Schedules.tsx b/grafana-plugin/src/pages/schedules/Schedules.tsx index 45e77c57ed..7a16317d09 100644 --- a/grafana-plugin/src/pages/schedules/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules/Schedules.tsx @@ -386,6 +386,12 @@ class _SchedulesPage extends React.Component Date: Thu, 30 May 2024 11:49:04 +0100 Subject: [PATCH 27/28] fix typo --- .../src/containers/RotationForm/ScheduleOverrideForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx index 24659b1690..3a10da6827 100644 --- a/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/ScheduleOverrideForm.tsx @@ -79,7 +79,7 @@ export const ScheduleOverrideForm: FC = (props) => { useEffect(() => { (async () => { if (isOpen) { - const elm = await waitForElement(`#${HTML_ID.OVERRIDES_AND_SWAPS}`); + const elm = await waitForElement(`#${HTML_ID.SCHEDULE_OVERRIDES_AND_SWAPS}`); const modal = document.querySelector(`.${cx('draggable')}`) as HTMLDivElement; const coords = getCoords(elm); const offsetTop = Math.min( From 6c4a0227ff4afe76b0184d914c6fe37476bd1da3 Mon Sep 17 00:00:00 2001 From: Maxim Mordasov Date: Thu, 30 May 2024 12:02:58 +0100 Subject: [PATCH 28/28] disable scheduleView.test.ts --- grafana-plugin/e2e-tests/schedules/scheduleView.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts index 7d67a772b0..1def16456e 100644 --- a/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts +++ b/grafana-plugin/e2e-tests/schedules/scheduleView.test.ts @@ -6,7 +6,7 @@ import { expect, test } from '../fixtures'; import { generateRandomValue } from '../utils/forms'; import { createOnCallScheduleWithRotation } from '../utils/schedule'; -test('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { +test.skip('schedule view (week/2 weeks/month) toggler works', async ({ adminRolePage }) => { const { page, userName } = adminRolePage; const onCallScheduleName = generateRandomValue();