From 8d5299c47610039bc044476d161ee3cca40f88fd Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 5 Oct 2022 11:32:10 +0300 Subject: [PATCH 1/7] add settings button to new schedules list --- .../src/components/Table/Table.module.css | 4 ++ grafana-plugin/src/components/Table/Table.tsx | 3 +- .../components/WithConfirm/WithConfirm.tsx | 4 +- .../ScheduleForm/ScheduleForm.config.ts | 31 +++++++++----- .../src/pages/schedules_NEW/Schedules.tsx | 41 ++++++++++++++++--- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/grafana-plugin/src/components/Table/Table.module.css b/grafana-plugin/src/components/Table/Table.module.css index 50e50ffc..7638849b 100644 --- a/grafana-plugin/src/components/Table/Table.module.css +++ b/grafana-plugin/src/components/Table/Table.module.css @@ -6,6 +6,10 @@ width: 100%; } +.root table tbody tr.row-even { + background: var(--background-secondary); +} + .root tr { min-height: 56px; } diff --git a/grafana-plugin/src/components/Table/Table.tsx b/grafana-plugin/src/components/Table/Table.tsx index dbc5e652..60d184cc 100644 --- a/grafana-plugin/src/components/Table/Table.tsx +++ b/grafana-plugin/src/components/Table/Table.tsx @@ -51,10 +51,11 @@ const GTable: FC = (props) => { (index % 2 === 0 ? cx('row-even') : cx('row-odd'))} {...restProps} /> {pagination && ( diff --git a/grafana-plugin/src/components/WithConfirm/WithConfirm.tsx b/grafana-plugin/src/components/WithConfirm/WithConfirm.tsx index 0419e8f3..67a53cd7 100644 --- a/grafana-plugin/src/components/WithConfirm/WithConfirm.tsx +++ b/grafana-plugin/src/components/WithConfirm/WithConfirm.tsx @@ -20,7 +20,9 @@ const WithConfirm = (props: WithConfirmProps) => { const [showConfirmation, setShowConfirmation] = useState(false); - const onClickCallback = useCallback(() => { + const onClickCallback = useCallback((event) => { + event.stopPropagation(); + setShowConfirmation(true); }, []); diff --git a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts index fe40a251..a3b92a0b 100644 --- a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts +++ b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts @@ -5,16 +5,6 @@ import { PRIVATE_CHANNEL_NAME } from 'models/slack_channel/slack_channel.config' import { DEFAULT_USER_ROLES } from 'models/user/user.config'; const commonFields: FormItem[] = [ - { - name: 'ical_url_overrides', - label: 'Overrides schedule iCal URL ', - type: FormItemType.TextArea, - description: - 'You can use an override calendar to share with your team members. Users can add \n' + - 'events to this calendar, and they will override existing events in the primary \n' + - 'calendar. The iCal URL for your override calendar can be found in the calendar \n' + - 'integration settings of your calendar service.', - }, { name: 'slack_channel_id', label: 'Slack channel', @@ -128,6 +118,16 @@ export const iCalForm: { name: string; fields: FormItem[] } = { 'access. The iCal URL for your primary calendar can be found in the calendar \n' + 'integration settings of your calendar service.', }, + { + name: 'ical_url_overrides', + label: 'Overrides schedule iCal URL ', + type: FormItemType.TextArea, + description: + 'You can use an override calendar to share with your team members. Users can add \n' + + 'events to this calendar, and they will override existing events in the primary \n' + + 'calendar. The iCal URL for your override calendar can be found in the calendar \n' + + 'integration settings of your calendar service.', + }, ...commonFields, ], }; @@ -140,6 +140,16 @@ export const calendarForm: { name: string; fields: FormItem[] } = { type: FormItemType.Input, validation: { required: true }, }, + { + name: 'ical_url_overrides', + label: 'Overrides schedule iCal URL ', + type: FormItemType.TextArea, + description: + 'You can use an override calendar to share with your team members. Users can add \n' + + 'events to this calendar, and they will override existing events in the primary \n' + + 'calendar. The iCal URL for your override calendar can be found in the calendar \n' + + 'integration settings of your calendar service.', + }, ...commonFields, ], }; @@ -152,5 +162,6 @@ export const apiForm: { name: string; fields: FormItem[] } = { type: FormItemType.Input, validation: { required: true }, }, + ...commonFields, ], }; diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index aa979db6..f20755d7 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { SyntheticEvent } from 'react'; import { getLocationSrv } from '@grafana/runtime'; import { Button, HorizontalGroup, IconButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; @@ -19,12 +19,15 @@ import TimelineMarks from 'components/TimelineMarks/TimelineMarks'; import UserTimezoneSelect from 'components/UserTimezoneSelect/UserTimezoneSelect'; import WithConfirm from 'components/WithConfirm/WithConfirm'; import ScheduleFinal from 'containers/Rotations/ScheduleFinal'; +import ScheduleForm from 'containers/ScheduleForm/ScheduleForm'; +import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { getFromString } from 'models/schedule/schedule.helpers'; import { Schedule, ScheduleType } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { getStartOfWeek } from 'pages/schedule/Schedule.helpers'; import { AppFeature } from 'state/features'; import { WithStoreProps } from 'state/types'; +import { UserAction } from 'state/userAction'; import { withMobXProviderContext } from 'state/withStore'; import styles from './Schedules.module.css'; @@ -38,6 +41,7 @@ interface SchedulesPageState { filters: SchedulesFiltersType; showNewScheduleSelector: boolean; expandedRowKeys: Array; + scheduleIdToEdit?: Schedule['id']; } @observer @@ -51,6 +55,7 @@ class SchedulesPage extends React.Component )} + {scheduleIdToEdit && ( + { + this.setState({ scheduleIdToEdit: undefined }); + }} + /> + )} ); } @@ -328,18 +342,33 @@ class SchedulesPage extends React.Component */} - - - + + + + + + + + ); }; + getEditScheduleClickHandler = (id: Schedule['id']) => { + return (event) => { + event.stopPropagation(); + + this.setState({ scheduleIdToEdit: id }); + }; + }; + getDeleteScheduleClickHandler = (id: Schedule['id']) => { const { store } = this.props; const { scheduleStore } = store; - return () => { + return (event: SyntheticEvent) => { + event.stopPropagation(); + scheduleStore.delete(id).then(this.update); }; }; From 5b38b8deafb90a865b35b99f9d73dcc334caab12 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 11 Oct 2022 12:31:15 +0100 Subject: [PATCH 2/7] auto tune shift end, allow scrolling behind modal --- .../src/components/Modal/Modal.module.css | 12 ++- grafana-plugin/src/components/Modal/Modal.tsx | 3 +- .../src/components/Table/Table.module.css | 8 +- grafana-plugin/src/components/Table/Table.tsx | 26 +++--- .../TimelineMarks/TimelineMarks.tsx | 6 +- .../RotationForm/DateTimePicker.tsx | 26 +++--- .../containers/RotationForm/RotationForm.tsx | 18 +++-- .../src/containers/Rotations/Rotations.tsx | 45 ++++++++--- .../Rotations/ScheduleOverrides.tsx | 28 ++++++- .../UsersTimezones/UsersTimezones.module.css | 4 +- .../src/pages/schedule/Schedule.module.css | 5 +- .../src/pages/schedule/Schedule.tsx | 80 +++++++++++-------- .../pages/schedules_NEW/Schedules.module.css | 4 + .../src/pages/schedules_NEW/Schedules.tsx | 40 +++++++++- 14 files changed, 211 insertions(+), 94 deletions(-) diff --git a/grafana-plugin/src/components/Modal/Modal.module.css b/grafana-plugin/src/components/Modal/Modal.module.css index 683a78ad..d4cafe6a 100644 --- a/grafana-plugin/src/components/Modal/Modal.module.css +++ b/grafana-plugin/src/components/Modal/Modal.module.css @@ -17,17 +17,21 @@ border: var(--border-weak); box-shadow: var(--shadows-z3); border-radius: 2px; + z-index: 10; } +/* + .overlay { - position: fixed; + position: relative; inset: 0; z-index: 10; - - /* background-color: rgba(0, 0, 0, 0.45); - backdrop-filter: blur(1px); */ + background-color: rgba(0, 0, 0, 0.45); + backdrop-filter: blur(1px); } .body-open { overflow: hidden; } + + */ diff --git a/grafana-plugin/src/components/Modal/Modal.tsx b/grafana-plugin/src/components/Modal/Modal.tsx index 869a3a25..e5c08d9f 100644 --- a/grafana-plugin/src/components/Modal/Modal.tsx +++ b/grafana-plugin/src/components/Modal/Modal.tsx @@ -39,7 +39,8 @@ const Modal: FC> = (props) => { contentLabel={title} className={cx('root')} overlayClassName={cx('overlay')} - bodyOpenClassName={cx('body-open')} + overlayElement={(props, contentElement) => contentElement} // render without overlay to allow body scroll + /* bodyOpenClassName={cx('body-open')} */ contentElement={contentElement} > {children} diff --git a/grafana-plugin/src/components/Table/Table.module.css b/grafana-plugin/src/components/Table/Table.module.css index 7638849b..e4ba3005 100644 --- a/grafana-plugin/src/components/Table/Table.module.css +++ b/grafana-plugin/src/components/Table/Table.module.css @@ -24,7 +24,8 @@ .root td { min-height: 60px; - padding: 10px 0; + padding-top: 10px; + padding-bottom: 10px; } .pagination { @@ -41,6 +42,11 @@ transition: transform 0.2s; } +/* to allow expand on expand-button click */ +.root table :global(.rc-table-row-expand-icon-cell) > span { + pointer-events: none; +} + .expand-icon__expanded { transform: rotate(0deg); } diff --git a/grafana-plugin/src/components/Table/Table.tsx b/grafana-plugin/src/components/Table/Table.tsx index 60d184cc..94abe0fb 100644 --- a/grafana-plugin/src/components/Table/Table.tsx +++ b/grafana-plugin/src/components/Table/Table.tsx @@ -33,19 +33,25 @@ export interface Props extends TableProps { } const GTable: FC = (props) => { - const { columns, data, className, pagination, loading, rowKey, expandable, ...restProps } = props; + const { columns, data, className, pagination, loading, rowKey, expandable: expandableProp, ...restProps } = props; const { page, total: numberOfPages, onChange: onNavigate } = pagination || {}; - if (expandable) { - expandable.expandIcon = ({ expanded, record }) => { - return ( -
- -
- ); - }; - } + const expandable = useMemo(() => { + return expandableProp + ? { + ...expandableProp, + expandIcon: ({ expanded, record }) => { + return ( +
+ +
+ ); + }, + expandedRowClassName: (record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd')), + } + : null; + }, [expandableProp]); return ( diff --git a/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx index 1ae46c1e..0ad40e76 100644 --- a/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx @@ -17,6 +17,8 @@ const cx = cn.bind(styles); const TimelineMarks: FC = (props) => { const { startMoment, debug } = props; + const currentMoment = useMemo(() => dayjs(), []); + const momentsToRender = useMemo(() => { const hoursToSplit = 12; @@ -63,7 +65,9 @@ const TimelineMarks: FC = (props) => { return (
- {m.moment.format('ddd D MMM')} + + {m.moment.format('ddd D MMM')} +
{m.moments.map((mm, j) => ( diff --git a/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx b/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx index 1857b5c7..c8174b94 100644 --- a/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx +++ b/grafana-plugin/src/containers/RotationForm/DateTimePicker.tsx @@ -44,23 +44,19 @@ const DateTimePicker = (props: UserTooltipProps) => { const minDate = useMemo(() => (minMoment ? toDate(minMoment, timezone) : undefined), [minMoment, timezone]); - const handleDateChange = useCallback( - (newDate: Date) => { - const localMoment = dayjs().tz(timezone).utcOffset() === 0 ? dayjs().utc() : dayjs().tz(timezone); + const handleDateChange = (newDate: Date) => { + const localMoment = dayjs().tz(timezone).utcOffset() === 0 ? dayjs().utc() : dayjs().tz(timezone); - const newValue = localMoment - .set('year', newDate.getFullYear()) - .set('month', newDate.getMonth()) - .set('date', newDate.getDate()) - .set('hour', value.getHours()) - .set('minute', value.getMinutes()) - .set('second', value.getSeconds()); - - onChange(newValue); - }, - [value] - ); + const newValue = localMoment + .set('year', newDate.getFullYear()) + .set('month', newDate.getMonth()) + .set('date', newDate.getDate()) + .set('hour', value.getHours()) + .set('minute', value.getMinutes()) + .set('second', value.getSeconds()); + onChange(newValue); + }; const handleTimeChange = useCallback( (newMoment: DateTime) => { const localMoment = dayjs().tz(timezone).utcOffset() === 0 ? dayjs().utc() : dayjs().tz(timezone); diff --git a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx index ffc2ada9..c370c2b8 100644 --- a/grafana-plugin/src/containers/RotationForm/RotationForm.tsx +++ b/grafana-plugin/src/containers/RotationForm/RotationForm.tsx @@ -96,6 +96,16 @@ const RotationForm: FC = observer((props) => { } }, [rotationStart, shiftStart]); + const updateShiftStart = useCallback( + (value) => { + const diff = shiftEnd.diff(shiftStart); + + setShiftStart(value); + setShiftEnd(value.add(diff)); + }, + [shiftStart, shiftEnd] + ); + const store = useStore(); const shift = store.scheduleStore.shifts[shiftId]; @@ -247,8 +257,6 @@ const RotationForm: FC = observer((props) => { const isFormValid = useMemo(() => userGroups.some((group) => group.length), [userGroups]); - const moment = dayjs(); - return ( = observer((props) => { className={cx('date-time-picker')} label={ - Shift start + Parent shift start } > - + - Shift end + Parent shift end } > diff --git a/grafana-plugin/src/containers/Rotations/Rotations.tsx b/grafana-plugin/src/containers/Rotations/Rotations.tsx index 90f1cc04..dc324eae 100644 --- a/grafana-plugin/src/containers/Rotations/Rotations.tsx +++ b/grafana-plugin/src/containers/Rotations/Rotations.tsx @@ -37,6 +37,7 @@ interface RotationsProps extends WithStoreProps { onCreate: () => void; onUpdate: () => void; onDelete: () => void; + disabled: boolean; } interface RotationsState { @@ -60,8 +61,8 @@ class Rotations extends Component { onUpdate, onDelete, store, - onClick, shiftIdToShowRotationForm, + disabled, } = this.props; const { layerPriority, shiftMomentToShowRotationForm } = this.state; @@ -97,13 +98,19 @@ class Rotations extends Component { Rotations
- + {disabled ? ( + + ) : ( + + )}
@@ -227,19 +234,35 @@ class Rotations extends Component { } onRotationClick = (shiftId: Shift['id'], moment?: dayjs.Dayjs) => { + const { disabled } = this.props; + + if (disabled) { + return; + } + this.setState({ shiftMomentToShowRotationForm: moment }, () => { this.onShowRotationForm(shiftId); }); }; handleAddLayer = (layerPriority: number, moment?: dayjs.Dayjs) => { + const { disabled } = this.props; + + if (disabled) { + return; + } + this.setState({ layerPriority, shiftMomentToShowRotationForm: moment }, () => { this.onShowRotationForm('new'); }); }; handleAddRotation = (option: SelectableValue) => { - const { startMoment } = this.props; + const { startMoment, disabled } = this.props; + + if (disabled) { + return; + } this.setState( { @@ -253,8 +276,6 @@ class Rotations extends Component { }; hideRotationForm = () => { - const { store } = this.props; - this.setState( { layerPriority: undefined, @@ -267,7 +288,7 @@ class Rotations extends Component { }; onShowRotationForm = (shiftId: Shift['id']) => { - const { onShowRotationForm } = this.props; + const { onShowRotationForm, disabled } = this.props; onShowRotationForm(shiftId); }; diff --git a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx index f6ba5f3b..2933386a 100644 --- a/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx +++ b/grafana-plugin/src/containers/Rotations/ScheduleOverrides.tsx @@ -33,6 +33,7 @@ interface ScheduleOverridesProps extends WithStoreProps { onCreate: () => void; onUpdate: () => void; onDelete: () => void; + disabled: boolean; } interface ScheduleOverridesState { @@ -46,8 +47,17 @@ class ScheduleOverrides extends Component
- @@ -155,13 +165,23 @@ class ScheduleOverrides extends Component { + const { disabled } = this.props; + + if (disabled) { + return; + } + this.setState({ shiftMomentToShowOverrideForm: moment }, () => { this.onShowRotationForm(shiftId); }); }; handleAddOverride = () => { - const { startMoment } = this.props; + const { startMoment, disabled } = this.props; + + if (disabled) { + return; + } this.setState({ shiftMomentToShowOverrideForm: startMoment }, () => { this.onShowRotationForm('new'); diff --git a/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.module.css b/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.module.css index 94166b47..746f8609 100644 --- a/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.module.css +++ b/grafana-plugin/src/containers/UsersTimezones/UsersTimezones.module.css @@ -1,5 +1,5 @@ .root { - border: var(--border-medium); + border: var(--border-weak); display: flex; flex-direction: column; background: var(--background-secondary); @@ -15,7 +15,7 @@ font-size: 19px; line-height: 24px; color: rgba(204, 204, 220, 0.65); - margin: 16px 0; + margin-top: 16px; } .current-time { diff --git a/grafana-plugin/src/pages/schedule/Schedule.module.css b/grafana-plugin/src/pages/schedule/Schedule.module.css index 09d0aa98..27ca691f 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.module.css +++ b/grafana-plugin/src/pages/schedule/Schedule.module.css @@ -4,7 +4,7 @@ margin: 0 auto; margin-top: 24px; - --rotations-border: var(--border-medium); + --rotations-border: var(--border-weak); --rotations-background: var(--background-secondary); } @@ -19,7 +19,6 @@ .users-timezones { width: 100%; - margin-bottom: 16px; } .controls { @@ -29,7 +28,7 @@ .rotations { display: flex; flex-direction: column; - gap: 20px; + gap: 16px; position: relative; width: 100%; } diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 8e05ea45..274f465c 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -114,7 +114,7 @@ class SchedulePage extends React.Component - + {schedule?.name} @@ -140,7 +140,7 @@ class SchedulePage extends React.Component tooltipContent="Schedule has unassigned time periods during next 7 days" />*/} - + {users && ( Current timezone: @@ -151,17 +151,16 @@ class SchedulePage extends React.Component {/* - */} - - - + */} + + + + + + - - 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. -
onTzChange={this.handleTimezoneChange} />
-
- - - - - - + + + + + + {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} + - - {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} - - - {/* + {/* onChange={this.handleRenderTypeChange} /> */} - -
- {/*
*/} -
+ +
onDelete={this.handleDeleteRotation} shiftIdToShowRotationForm={shiftIdToShowRotationForm} onShowRotationForm={this.handleShowRotationForm} + disabled={shiftIdToShowRotationForm || shiftIdToShowOverridesForm} /> onDelete={this.handleDeleteOverride} shiftIdToShowRotationForm={shiftIdToShowOverridesForm} onShowRotationForm={this.handleShowOverridesForm} + disabled={shiftIdToShowRotationForm || shiftIdToShowOverridesForm} />
@@ -260,17 +262,29 @@ class SchedulePage extends React.Component const shift = await scheduleStore.updateOncallShift(shiftId); if (shift.type === 2) { - this.setState({ shiftIdToShowRotationForm: shiftId }); + this.handleShowRotationForm(shiftId); } else if (shift.type === 3) { - this.setState({ shiftIdToShowOverridesForm: shiftId }); + this.handleShowOverridesForm(shiftId); } }; handleShowRotationForm = (shiftId: Shift['id'] | 'new') => { + const { shiftIdToShowRotationForm, shiftIdToShowOverridesForm } = this.state; + + if (shiftId && (shiftIdToShowRotationForm || shiftIdToShowOverridesForm)) { + return; + } + this.setState({ shiftIdToShowRotationForm: shiftId }); }; handleShowOverridesForm = (shiftId: Shift['id'] | 'new') => { + const { shiftIdToShowRotationForm, shiftIdToShowOverridesForm } = this.state; + + if (shiftId && (shiftIdToShowRotationForm || shiftIdToShowOverridesForm)) { + return; + } + this.setState({ shiftIdToShowOverridesForm: shiftId }); }; diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.module.css b/grafana-plugin/src/pages/schedules_NEW/Schedules.module.css index 4857e7cc..380b25a3 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.module.css +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.module.css @@ -15,6 +15,10 @@ padding-left: 20px; } +.root .buttons { + padding-right: 10px; +} + /* .root .expanded-row { background: var(--secondary-background); diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index f20755d7..d08e7e5a 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -23,6 +23,7 @@ import ScheduleForm from 'containers/ScheduleForm/ScheduleForm'; import { WithPermissionControl } from 'containers/WithPermissionControl/WithPermissionControl'; import { getFromString } from 'models/schedule/schedule.helpers'; import { Schedule, ScheduleType } from 'models/schedule/schedule.types'; +import { getSlackChannelName } from 'models/slack_channel/slack_channel.helpers'; import { Timezone } from 'models/timezone/timezone.types'; import { getStartOfWeek } from 'pages/schedule/Schedule.helpers'; import { AppFeature } from 'state/features'; @@ -78,6 +79,12 @@ class SchedulesPage extends React.Component { + type tTypeToVerbal = { + [key: number]: string; + }; + const typeToVerbal: tTypeToVerbal = { 0: 'API/Terraform', 1: 'Ical', 2: 'Web' }; + return typeToVerbal[value]; + }; + renderStatus = (item: Schedule) => { const { store: { scheduleStore }, @@ -326,6 +352,14 @@ class SchedulesPage extends React.Component { + return getSlackChannelName(value.slack_channel) || '-'; + }; + + renderUserGroup = (value: Schedule) => { + return value.user_group?.handle || '-'; + }; + /* renderChatOps = (item: Schedule) => { return item.chatOps; }; */ From e1360ced6c61bcab0da15f898668dd08af23b43e Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 12 Oct 2022 08:54:20 +0100 Subject: [PATCH 3/7] minor fix --- .../TimelineMarks/TimelineMarks.tsx | 4 +- .../ScheduleForm/ScheduleForm.config.ts | 50 ++-- .../containers/ScheduleForm/ScheduleForm.tsx | 4 +- .../src/pages/schedule/Schedule.tsx | 218 ++++++++++-------- 4 files changed, 156 insertions(+), 120 deletions(-) diff --git a/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx b/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx index 0ad40e76..82244568 100644 --- a/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx +++ b/grafana-plugin/src/components/TimelineMarks/TimelineMarks.tsx @@ -62,10 +62,12 @@ const TimelineMarks: FC = (props) => { )} {momentsToRender.map((m, i) => { + const isCurrentDay = currentMoment.isSame(m.moment, 'day'); + return (
- + {m.moment.format('ddd D MMM')}
diff --git a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts index a3b92a0b..62b6df1f 100644 --- a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts +++ b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.config.ts @@ -5,6 +5,18 @@ import { PRIVATE_CHANNEL_NAME } from 'models/slack_channel/slack_channel.config' import { DEFAULT_USER_ROLES } from 'models/user/user.config'; const commonFields: FormItem[] = [ + { + name: 'team', + label: 'Assign to team', + type: FormItemType.GSelect, + extra: { + modelName: 'grafanaTeamStore', + displayField: 'name', + valueField: 'id', + showSearch: true, + allowClear: true, + }, + }, { name: 'slack_channel_id', label: 'Slack channel', @@ -19,6 +31,19 @@ const commonFields: FormItem[] = [ description: 'Calendar parsing errors and notifications about the new on-call shift will be published in this channel.', }, + { + name: 'user_group', + label: 'Slack user group', + type: FormItemType.GSelect, + extra: { + modelName: 'userGroupStore', + displayField: 'handle', + showSearch: true, + allowClear: true, + }, + description: + 'Group members will be automatically updated with current on-call. In case you want to ping on-call with @group_name.', + }, { name: 'notify_oncall_shift_freq', label: 'Notification frequency', @@ -67,37 +92,12 @@ const commonFields: FormItem[] = [ }, description: 'Specify how to notify a team member when their shift is the next one scheduled', }, - { - name: 'user_group', - label: 'Slack user group', - type: FormItemType.GSelect, - extra: { - modelName: 'userGroupStore', - displayField: 'handle', - showSearch: true, - allowClear: true, - }, - description: - 'Group members will be automatically updated with current on-call. In case you want to ping on-call with @group_name.', - }, // { // name: 'send_empty_shifts_report', // normalize: (value) => Boolean(value), // label: 'Send reports about empty shifts to Slack', // type: FormItemType.Switch, // }, - { - name: 'team', - label: 'Assign to team', - type: FormItemType.GSelect, - extra: { - modelName: 'grafanaTeamStore', - displayField: 'name', - valueField: 'id', - showSearch: true, - allowClear: true, - }, - }, ]; export const iCalForm: { name: string; fields: FormItem[] } = { diff --git a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx index 2c58562e..fe84dfeb 100644 --- a/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx +++ b/grafana-plugin/src/containers/ScheduleForm/ScheduleForm.tsx @@ -78,10 +78,10 @@ const ScheduleForm = observer((props: ScheduleFormProps) => { >
- + {/* Manage on-call schedules using your favourite calendar app, such as Google Calendar or Microsoft Outlook. To schedule on-call shifts create a new calendar and use events with the teammates usernames - + */} - - - + + + + + + {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} + - - {startMoment.format('DD MMM')} - {startMoment.add(6, 'day').format('DD MMM')} - - - {/* + {/* onChange={this.handleRenderTypeChange} /> */} - + +
+ + +
- - - - - - + + + {showEditForm && ( + { + this.setState({ showEditForm: false }); + }} + /> + )} + ); } + update = () => { + const { store, query } = this.props; + const { id: scheduleId } = query; + const { scheduleStore } = store; + + return scheduleStore.updateItem(scheduleId); + }; + handleShowForm = async (shiftId: Shift['id'] | 'new') => { const { store: { scheduleStore }, From 2dcac2b9a9834dd4532df2932040b74e26bc5ccd Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 13 Oct 2022 11:02:00 +0100 Subject: [PATCH 4/7] review fixes --- grafana-plugin/src/components/Table/Table.tsx | 12 ++++++------ grafana-plugin/src/pages/schedule/Schedule.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/src/components/Table/Table.tsx b/grafana-plugin/src/components/Table/Table.tsx index 94abe0fb..8c656de5 100644 --- a/grafana-plugin/src/components/Table/Table.tsx +++ b/grafana-plugin/src/components/Table/Table.tsx @@ -33,14 +33,14 @@ export interface Props extends TableProps { } const GTable: FC = (props) => { - const { columns, data, className, pagination, loading, rowKey, expandable: expandableProp, ...restProps } = props; + const { columns, data, className, pagination, loading, rowKey, expandable, ...restProps } = props; const { page, total: numberOfPages, onChange: onNavigate } = pagination || {}; - const expandable = useMemo(() => { - return expandableProp + const expandableFn = useMemo(() => { + return expandable ? { - ...expandableProp, + ...expandable, expandIcon: ({ expanded, record }) => { return (
@@ -51,7 +51,7 @@ const GTable: FC = (props) => { expandedRowClassName: (record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd')), } : null; - }, [expandableProp]); + }, [expandable]); return ( @@ -60,7 +60,7 @@ const GTable: FC = (props) => { className={cx('root', className)} columns={columns} data={data} - expandable={expandable} + expandable={expandableFn} rowClassName={(record, index) => (index % 2 === 0 ? cx('row-even') : cx('row-odd'))} {...restProps} /> diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index 0bb145d2..c5427f37 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -30,7 +30,7 @@ import ScheduleFinal from 'containers/Rotations/ScheduleFinal'; import ScheduleOverrides from 'containers/Rotations/ScheduleOverrides'; import ScheduleForm from 'containers/ScheduleForm/ScheduleForm'; import UsersTimezones from 'containers/UsersTimezones/UsersTimezones'; -import { Shift } from 'models/schedule/schedule.types'; +import { ScheduleType, Shift } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; From b4a311e6eda7060da0ab7cc98d2cdbbb847c3ce9 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 13 Oct 2022 12:30:01 +0100 Subject: [PATCH 5/7] fix props --- grafana-plugin/src/pages/schedule/Schedule.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index ba6d79cd..f1b9eedb 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -83,7 +83,8 @@ class SchedulePage extends React.Component render() { const { query: { id: scheduleId }, - store, } = this.props; + store, + } = this.props; const { startMoment, @@ -160,6 +161,8 @@ class SchedulePage extends React.Component
Date: Thu, 13 Oct 2022 15:03:27 +0300 Subject: [PATCH 6/7] avatar fix --- .../src/pages/schedule/Schedule.helpers.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/grafana-plugin/src/pages/schedule/Schedule.helpers.ts b/grafana-plugin/src/pages/schedule/Schedule.helpers.ts index 1f9e98e5..bd23489c 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.helpers.ts +++ b/grafana-plugin/src/pages/schedule/Schedule.helpers.ts @@ -2,7 +2,7 @@ import dayjs from 'dayjs'; import { findColor } from 'containers/Rotations/Rotations.helpers'; import { getLayersFromStore, getOverridesFromStore, getShiftsFromStore } from 'models/schedule/schedule.helpers'; -import { Event } from 'models/schedule/schedule.types'; +import { Event, Layer } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { RootStore } from 'state'; @@ -18,7 +18,6 @@ export const getDateTime = (date: string) => { return dayjs(date); }; - export const getColorSchemeMappingForUsers = ( store: RootStore, scheduleId: string, @@ -26,15 +25,19 @@ export const getColorSchemeMappingForUsers = ( ): { [userId: string]: Set } => { const usersColorSchemeHash: { [userId: string]: Set } = {}; - const shifts = getShiftsFromStore(store, scheduleId, startMoment); - const layers = getLayersFromStore(store, scheduleId, startMoment); + const layers: Layer[] = getLayersFromStore(store, scheduleId, startMoment); const overrides = getOverridesFromStore(store, scheduleId, startMoment); - if (!shifts?.length || !layers?.length) { + if (!layers?.length) { return usersColorSchemeHash; } - shifts.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId)); + const shiftsFromLayers = layers.reduce((prev, current) => { + prev.push(...current.shifts); + return prev; + }, []); + + shiftsFromLayers.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId)); return usersColorSchemeHash; @@ -49,4 +52,4 @@ export const getColorSchemeMappingForUsers = ( }); }); } -} +}; From d37779e3b06fa21d0c5bd3c06457d8107754fe58 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Thu, 13 Oct 2022 15:05:36 +0300 Subject: [PATCH 7/7] update --- grafana-plugin/src/pages/schedule/Schedule.helpers.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/grafana-plugin/src/pages/schedule/Schedule.helpers.ts b/grafana-plugin/src/pages/schedule/Schedule.helpers.ts index bd23489c..0b54681d 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.helpers.ts +++ b/grafana-plugin/src/pages/schedule/Schedule.helpers.ts @@ -25,19 +25,21 @@ export const getColorSchemeMappingForUsers = ( ): { [userId: string]: Set } => { const usersColorSchemeHash: { [userId: string]: Set } = {}; + const finalScheduleShifts = getShiftsFromStore(store, scheduleId, startMoment); const layers: Layer[] = getLayersFromStore(store, scheduleId, startMoment); const overrides = getOverridesFromStore(store, scheduleId, startMoment); - if (!layers?.length) { + if (!finalScheduleShifts?.length || !layers?.length) { return usersColorSchemeHash; } - const shiftsFromLayers = layers.reduce((prev, current) => { + const rotationShifts = layers.reduce((prev, current) => { prev.push(...current.shifts); return prev; }, []); - shiftsFromLayers.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId)); + finalScheduleShifts.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId)); + rotationShifts.forEach(({ shiftId, events }) => populateUserHashSet(events, shiftId)); return usersColorSchemeHash;