diff --git a/grafana-plugin/src/GrafanaPluginRootPage.tsx b/grafana-plugin/src/GrafanaPluginRootPage.tsx index 4863734c..dcf33cbe 100644 --- a/grafana-plugin/src/GrafanaPluginRootPage.tsx +++ b/grafana-plugin/src/GrafanaPluginRootPage.tsx @@ -3,6 +3,9 @@ import React, { useEffect, useMemo } from 'react'; import { AppRootProps } from '@grafana/data'; import { Button, HorizontalGroup, LinkButton, VerticalGroup } from '@grafana/ui'; import dayjs from 'dayjs'; +import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'; +import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; +import localeData from 'dayjs/plugin/localeData'; import timezone from 'dayjs/plugin/timezone'; import utc from 'dayjs/plugin/utc'; import weekday from 'dayjs/plugin/weekday'; @@ -21,6 +24,9 @@ import { useNavModel } from 'utils/hooks'; dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(weekday); +dayjs.extend(localeData); +dayjs.extend(isSameOrBefore); +dayjs.extend(isSameOrAfter); // dayjs().weekday(0); diff --git a/grafana-plugin/src/components/Rotations/Rotations.tsx b/grafana-plugin/src/components/Rotations/Rotations.tsx index 7da97723..c939298b 100644 --- a/grafana-plugin/src/components/Rotations/Rotations.tsx +++ b/grafana-plugin/src/components/Rotations/Rotations.tsx @@ -39,12 +39,12 @@ class Rotations extends Component { const layers = [ { id: 0, title: 'Layer 1' }, - { id: 1, title: 'Layer 2' }, + /* { id: 1, title: 'Layer 2' }, { id: 2, title: 'Layer 3' }, - { id: 3, title: 'Layer 4' }, + { id: 3, title: 'Layer 4' },*/ ]; - const rotations = [{}, {}]; + const rotations = [{} /*, {}*/]; return ( <> diff --git a/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.module.css b/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.module.css index b83c323b..91f29443 100644 --- a/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.module.css +++ b/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.module.css @@ -7,6 +7,11 @@ gap: 4px; } +.working-hours{ + position: absolute; + top: 0; + left: 0; +} .stack { display: flex; @@ -22,6 +27,7 @@ background: rgba(209, 14, 92, 0.2); border: 1px dashed #FF5286; color: rgba(209, 14, 92, .5); + visibility: hidden; } .root__inactive { diff --git a/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.tsx b/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.tsx index c2ad78eb..98d0715d 100644 --- a/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.tsx +++ b/grafana-plugin/src/components/ScheduleSlot/ScheduleSlot.tsx @@ -6,6 +6,7 @@ import { observer } from 'mobx-react'; import Line from 'components/ScheduleUserDetails/img/line.svg'; import Text from 'components/Text/Text'; +import WorkingHours from 'components/WorkingHours/WorkingHours'; import { Shift } from 'models/schedule/schedule.types'; import { User } from 'models/user/user.types'; import { useStore } from 'state/useStore'; @@ -57,7 +58,15 @@ const ScheduleSlot: FC = observer((props) => { backgroundColor: color, }} > -
+ {storeUser && ( + + )} {label && (
{label} diff --git a/grafana-plugin/src/components/WorkingHours/WorkingHours.config.ts b/grafana-plugin/src/components/WorkingHours/WorkingHours.config.ts new file mode 100644 index 00000000..085666a9 --- /dev/null +++ b/grafana-plugin/src/components/WorkingHours/WorkingHours.config.ts @@ -0,0 +1,9 @@ +export const default_working_hours = { + friday: [{ end: '17:00:00', start: '09:00:00' }], + monday: [{ end: '17:00:00', start: '09:00:00' }], + sunday: [], + tuesday: [{ end: '17:00:00', start: '09:00:00' }], + saturday: [], + thursday: [{ end: '17:00:00', start: '09:00:00' }], + wednesday: [{ end: '17:00:00', start: '09:00:00' }], +}; diff --git a/grafana-plugin/src/components/WorkingHours/WorkingHours.helpers.ts b/grafana-plugin/src/components/WorkingHours/WorkingHours.helpers.ts new file mode 100644 index 00000000..692cea35 --- /dev/null +++ b/grafana-plugin/src/components/WorkingHours/WorkingHours.helpers.ts @@ -0,0 +1,88 @@ +import dayjs from 'dayjs'; + +export const getWorkingMoments = ( + startMoment, + endMoment, + workingHours, + timezone, +) => { + const weekdays = dayjs.weekdays(); + + const dayOfWeekToStartIteration = startMoment.format('dddd'); + const weekDaysToIterateChunk = [ + dayOfWeekToStartIteration, + ...weekdays.slice(weekdays.indexOf(dayOfWeekToStartIteration) + 1), + ...weekdays.slice(0, weekdays.indexOf(dayOfWeekToStartIteration)), + ]; + + const weeks = endMoment.diff(startMoment, 'weeks'); + + const weekDaysToIterate = [...weekDaysToIterateChunk]; + for (let i = 0; i < weeks; i++) { + weekDaysToIterate.push(...weekDaysToIterateChunk); + } + + const workingMoments = []; + for (const [i, weekday] of weekDaysToIterate.entries()) { + for (const range of workingHours[weekday.toLowerCase()]) { + const rangeStartData = range.start; + const rangeEndData = range.end; + const [start_HH, start_mm, start_ss] = rangeStartData.split(':'); + const [end_HH, end_mm, end_ss] = rangeEndData.split(':'); + + const rangeStartMoment = dayjs(startMoment) + .tz(timezone) + .add(i, 'day') + .set('hour', Number(start_HH)) + .set('minute', Number(start_mm)) + .set('second', Number(start_ss)); + + const rangeEndMoment = dayjs(startMoment) + .tz(timezone) + .add(i, 'day') + .set('hour', Number(end_HH)) + .set('minute', Number(end_mm)) + .set('second', Number(end_ss)); + + if (rangeEndMoment.isSameOrBefore(startMoment)) { + continue; + } else if (rangeStartMoment.isSameOrAfter(endMoment)) { + continue; + } + + if ( + rangeStartMoment.isSameOrBefore(startMoment) && + rangeEndMoment.isSameOrAfter(startMoment) && + rangeEndMoment.isSameOrBefore(endMoment) + ) { + workingMoments.push({ start: startMoment, end: rangeEndMoment }); + } else if ( + rangeEndMoment.isSameOrAfter(endMoment) && + rangeStartMoment.isSameOrBefore(endMoment) && + rangeStartMoment.isSameOrAfter(startMoment) + ) { + workingMoments.push({ start: rangeStartMoment, end: endMoment }); + } else { + workingMoments.push({ start: rangeStartMoment, end: rangeEndMoment }); + } + } + } + + return workingMoments; +}; + +export const getNonWorkingMoments = (startMoment, endMoment, workingHours) => { + const nonWorkingMoments = [{ start: startMoment, end: endMoment }]; + + let lastNonWorkingRange = nonWorkingMoments[0]; + for (const [i, range] of workingHours.entries()) { + lastNonWorkingRange.end = range.start; + + lastNonWorkingRange = { start: range.end, end: undefined }; + nonWorkingMoments.push(lastNonWorkingRange); + } + + lastNonWorkingRange.end = endMoment; + + return nonWorkingMoments; +}; diff --git a/grafana-plugin/src/components/WorkingHours/WorkingHours.module.css b/grafana-plugin/src/components/WorkingHours/WorkingHours.module.css new file mode 100644 index 00000000..8b98de94 --- /dev/null +++ b/grafana-plugin/src/components/WorkingHours/WorkingHours.module.css @@ -0,0 +1,3 @@ +.root { + display: block; +} diff --git a/grafana-plugin/src/components/WorkingHours/WorkingHours.tsx b/grafana-plugin/src/components/WorkingHours/WorkingHours.tsx new file mode 100644 index 00000000..3f729216 --- /dev/null +++ b/grafana-plugin/src/components/WorkingHours/WorkingHours.tsx @@ -0,0 +1,89 @@ +import React, { FC, useMemo } from 'react'; + +import cn from 'classnames/bind'; +import dayjs from 'dayjs'; +import localeData from 'dayjs/plugin/localeData'; + +import { Timezone } from 'models/timezone/timezone.types'; + +import { default_working_hours } from './WorkingHours.config'; +import { getNonWorkingMoments, getWorkingMoments } from './WorkingHours.helpers'; + +import styles from './WorkingHours.module.css'; + +import { start } from 'repl'; + +interface WorkingHoursProps { + timezone: Timezone; + workingHours: any; + startMoment: dayjs.Dayjs; + duration: number; // in seconds + width: number; // in pixels + className: string; +} + +const cx = cn.bind(styles); + +const WorkingHours: FC = (props) => { + const { + timezone, + workingHours = default_working_hours, + startMoment = dayjs().utc().startOf('week'), + duration = 14 * 24 * 60 * 60, + className, + } = props; + + timezone = dayjs.tz.guess(); + + const endMoment = startMoment.add(duration, 'seconds'); + + const workingMoments = useMemo( + () => getWorkingMoments(startMoment, endMoment, workingHours, timezone), + [startMoment, endMoment, workingHours, timezone] + ); + + const nonWorkingMoments = getNonWorkingMoments(startMoment, endMoment, workingMoments); + + console.log(startMoment.tz(timezone).format('D MMM ddd HH:ss')); + + /*console.log( + workingMoments.map( + (range) => + `${range.start.tz(timezone).format('D MMM ddd HH:ss')} - ${range.end.tz(timezone).format('D MMM ddd HH:ss')}` + ) + ); + + console.log( + nonWorkingMoments.map( + (range) => + `${range.start.tz(timezone).format('D MMM ddd HH:ss')} - ${range.end.tz(timezone).format('D MMM ddd HH:ss')}` + ) + );*/ + + return ( + + + + + + + {nonWorkingMoments.map((moment, index) => { + const start = moment.start.diff(startMoment, 'seconds'); + const diff = moment.end.diff(moment.start, 'seconds'); + return ( + + ); + })} + + ); +}; + +export default WorkingHours; diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index f7426748..79a3b86a 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -118,20 +118,24 @@ export class ScheduleStore extends BaseStore { const response = await new Promise((resolve, reject) => { function getUsers() { const rnd = Math.random(); + /* if (rnd > 0.66) { return []; } +*/ const users = [ - 'UCXTPJYKQHFW6', - 'UFYP8IJV9BZDE', - 'U122EFECQFN9Y', - 'UZ2LWBDAZE962', - 'U87ZI7PRWF7K1', - 'U2VY9ZP5A1XKL', - 'UTA6SS7RL3HC7', - 'UAYAYSDVG5MYH', + 'UQEAACAGQ5JHL', + 'UEHYTCX4AMX75', + 'U3U8343UTJ91U', + 'UTNF7TCGBPADM', + 'UWPPUTZHCC9U5', + 'UDUG977U8V8AX', + 'UNN22BHCXZ6TR', + 'UTKBFZH8HM1TF', + 'U1DJX6WMFTWY7', + 'UPZ7AJPKVJL9K', ]; if (rnd > 0.33) { @@ -151,8 +155,9 @@ export class ScheduleStore extends BaseStore { const shifts = []; for (let i = 0; i < 14; i++) { shifts.push({ - start: dayjs(startMoment).add(3 * i, 'hour'), - duration: (Math.floor(Math.random() * 6) + 10) * 60 * 60, + start: dayjs(startMoment).add(12 * i, 'hour'), + //duration: (Math.floor(Math.random() * 6) + 10) * 60 * 60, + duration: 12 * 60 * 60, users: getUsers(), }); } diff --git a/grafana-plugin/src/models/user/user.ts b/grafana-plugin/src/models/user/user.ts index e1d66667..d9713f0b 100644 --- a/grafana-plugin/src/models/user/user.ts +++ b/grafana-plugin/src/models/user/user.ts @@ -100,7 +100,7 @@ export class UserStore extends BaseStore { ...acc, [item.pk]: { ...item, - tz: getRandomTimezone(), + timezone: getRandomTimezone(), working_hours: { monday: [{ start: '09:00:00', end: '18:00:00' }], tuesday: [{ start: '09:00:00', end: '18:00:00' }], @@ -116,8 +116,6 @@ export class UserStore extends BaseStore { ), }; - console.log(this.items); - this.searchResult = { count, results: results.map((item: User) => item.pk), diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css index ce2dcc89..c4a276b4 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ b/grafana-plugin/src/pages/schedule/Schedule.module.css @@ -4,6 +4,10 @@ margin-top: 24px; } +.header{ + position: sticky; +} + .desc{ width: 736px; } diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 5227d54e..56486caf 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -62,43 +62,45 @@ class SchedulePage extends React.Component return (
- - - - - - Schedule Team {query.id} - - Grafana 1 -
- Grafana 2 -
- Grafana 3 - - } - /> - +
+ + + + + + Schedule Team {query.id} + + Grafana 1 +
+ Grafana 2 +
+ Grafana 3 + + } + /> + +
+ + + + + + + + +
- - - - - - - - - - +
On-call Schedules. Use this to distribute notifications among team members you specified in the "Notify Users from on-call schedule" step in escalation chains.