diff --git a/grafana-plugin/src/components/ScheduleWarning/ScheduleWarning.tsx b/grafana-plugin/src/components/ScheduleWarning/ScheduleWarning.tsx new file mode 100644 index 00000000..f4ae16e9 --- /dev/null +++ b/grafana-plugin/src/components/ScheduleWarning/ScheduleWarning.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import { PENDING_COLOR, Tooltip, Icon } from '@grafana/ui'; + +import { Schedule } from 'models/schedule/schedule.types'; + +interface ScheduleWarningProps { + item: Schedule; +} + +const ScheduleWarning = (props: ScheduleWarningProps) => { + const { item } = props; + if (item.warnings.length > 0) { + const tooltipContent = ( +
+ {item.warnings.map((warning: string, key: number) => ( +

{warning}

+ ))} +
+ ); + return ( + + + + ); + } + + return null; +}; + +export default ScheduleWarning; diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx index dcb30d78..b1fa8834 100644 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx @@ -68,9 +68,8 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => { { value: ScheduleType.Calendar, }, ]} - value={value.type} + value={value?.type} onChange={handleTypeChange} /> diff --git a/grafana-plugin/src/models/schedule/schedule.ts b/grafana-plugin/src/models/schedule/schedule.ts index cee1deaa..1a5abad2 100644 --- a/grafana-plugin/src/models/schedule/schedule.ts +++ b/grafana-plugin/src/models/schedule/schedule.ts @@ -18,7 +18,7 @@ import { Rotation, RotationType, Schedule, ScheduleEvent, Shift, Event, Layer, S export class ScheduleStore extends BaseStore { @observable - searchResult: { [key: string]: Array } = {}; + searchResult: { results?: Array } = {}; @observable.shallow items: { [id: string]: Schedule } = {}; @@ -105,8 +105,11 @@ export class ScheduleStore extends BaseStore { } @action - async updateItems(query = '') { - const result = await makeRequest(this.path, { method: 'GET', params: { search: query } }); + async updateItems(f: any = { searchTerm: '', type: undefined }) { + // async updateItems(query = '') { + const filters = typeof f === 'string' ? { searchTerm: f } : f; + const { searchTerm: search, type } = filters; + const result = await makeRequest(this.path, { method: 'GET', params: { search: search, type } }); this.items = { ...this.items, @@ -118,10 +121,9 @@ export class ScheduleStore extends BaseStore { {} ), }; - this.searchResult = { ...this.searchResult, - [query]: result.map((item: Schedule) => item.id), + results: result.map((item: Schedule) => item.id), }; } @@ -136,12 +138,11 @@ export class ScheduleStore extends BaseStore { } } - getSearchResult(query = '') { - if (!this.searchResult[query]) { + getSearchResult() { + if (!this.searchResult.results) { return undefined; } - - return this.searchResult[query].map((scheduleId: Schedule['id']) => this.items[scheduleId]); + return this.searchResult?.results?.map((scheduleId: Schedule['id']) => this.items[scheduleId]); } @action diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index b49cb1bf..bebb0900 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -2,12 +2,14 @@ import React from 'react'; import { AppRootProps } from '@grafana/data'; import { getLocationSrv } from '@grafana/runtime'; -import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon } from '@grafana/ui'; +import { Button, HorizontalGroup, VerticalGroup, IconButton, ToolbarButton, Icon, Modal } from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; +import { omit } from 'lodash-es'; import { observer } from 'mobx-react'; import PluginLink from 'components/PluginLink/PluginLink'; +import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning'; import Text from 'components/Text/Text'; import UserTimezoneSelect from 'components/UserTimezoneSelect/UserTimezoneSelect'; import WithConfirm from 'components/WithConfirm/WithConfirm'; @@ -15,8 +17,9 @@ import Rotations from 'containers/Rotations/Rotations'; import ScheduleFinal from 'containers/Rotations/ScheduleFinal'; import ScheduleOverrides from 'containers/Rotations/ScheduleOverrides'; import ScheduleForm from 'containers/ScheduleForm/ScheduleForm'; +import ScheduleICalSettings from 'containers/ScheduleIcalLink/ScheduleIcalLink'; import UsersTimezones from 'containers/UsersTimezones/UsersTimezones'; -import { Shift } from 'models/schedule/schedule.types'; +import { Schedule, ScheduleType, Shift } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -24,7 +27,6 @@ import { withMobXProviderContext } from 'state/withStore'; import { getStartOfWeek } from './Schedule.helpers'; import styles from './Schedule.module.css'; - const cx = cn.bind(styles); interface SchedulePageProps extends AppRootProps, WithStoreProps {} @@ -37,6 +39,7 @@ interface SchedulePageState { shiftIdToShowOverridesForm?: Shift['id']; isLoading: boolean; showEditForm: boolean; + showScheduleICalSettings: boolean; } @observer @@ -53,6 +56,7 @@ class SchedulePage extends React.Component shiftIdToShowOverridesForm: undefined, isLoading: true, showEditForm: false, + showScheduleICalSettings: false, }; } @@ -89,6 +93,7 @@ class SchedulePage extends React.Component shiftIdToShowRotationForm, shiftIdToShowOverridesForm, showEditForm, + showScheduleICalSettings, } = this.state; const { scheduleStore, currentTimezone } = store; @@ -109,6 +114,7 @@ class SchedulePage extends React.Component {schedule?.name} + {schedule && } {users && ( @@ -118,6 +124,16 @@ class SchedulePage extends React.Component )} + {schedule?.type === ScheduleType.Ical && ( + + + + + )} }} /> )} + {showScheduleICalSettings && ( + this.setState({ showScheduleICalSettings: false })} + > + + + )} ); } @@ -371,6 +397,47 @@ class SchedulePage extends React.Component this.setState({ startMoment: startMoment.add(7, 'day') }, this.handleDateRangeUpdate); }; + handleExportClick = () => { + return () => { + this.setState({ showScheduleICalSettings: true }); + }; + }; + + handleReloadClick = (scheduleId: Schedule['id']) => { + const { store } = this.props; + + const { scheduleStore } = store; + + return async () => { + await scheduleStore.reloadIcal(scheduleId); + + scheduleStore.updateItem(scheduleId); + this.updateEventsFor(scheduleId); + }; + }; + + updateEventsFor = async (scheduleId: Schedule['id'], withEmpty = true, with_gap = true) => { + const { + store, + query: { id }, + } = this.props; + + const { scheduleStore } = store; + + store.scheduleStore.scheduleToScheduleEvents = omit(store.scheduleStore.scheduleToScheduleEvents, [scheduleId]); + + await scheduleStore.updateScheduleEvents( + scheduleId, + withEmpty, + with_gap, + dayjs().format('YYYY-MM-DD').toString(), + dayjs.tz.guess() + ); + + await store.scheduleStore.updateOncallShifts(id); + await this.updateEvents(); + }; + handleDelete = () => { const { store, diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index 6ceedaad..c180fb46 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -11,6 +11,7 @@ import Avatar from 'components/Avatar/Avatar'; import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector'; import PluginLink from 'components/PluginLink/PluginLink'; import ScheduleCounter from 'components/ScheduleCounter/ScheduleCounter'; +import ScheduleWarning from 'components/ScheduleWarning/ScheduleWarning'; import SchedulesFilters from 'components/SchedulesFilters_NEW/SchedulesFilters'; import { SchedulesFiltersType } from 'components/SchedulesFilters_NEW/SchedulesFilters.types'; import Table from 'components/Table/Table'; @@ -51,7 +52,7 @@ class SchedulesPage extends React.Component this.renderStatus(item), }, { width: '30%', @@ -107,6 +108,11 @@ class SchedulesPage extends React.Component schedule.type === ScheduleType.API) .filter( (schedule) => filters.status === 'all' || @@ -265,38 +270,52 @@ class SchedulesPage extends React.Component { + return ; + }; + renderStatus = (item: Schedule) => { const { store: { scheduleStore }, } = this.props; const relatedEscalationChains = scheduleStore.relatedEscalationChains[item.id]; - return ( - - {relatedEscalationChains ? ( - relatedEscalationChains.length ? ( - relatedEscalationChains.map((escalationChain) => ( - - {escalationChain.name} - - )) + {item.number_of_escalation_chains > 0 && ( + + {relatedEscalationChains ? ( + relatedEscalationChains.length ? ( + relatedEscalationChains.map((escalationChain) => ( +
+ + {escalationChain.name} + +
+ )) + ) : ( + 'Not used yet' + ) ) : ( - 'Not used yet' - ) - ) : ( - Loading related escalation chains.... - )} - - } - onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)} - /> + Loading related escalation chains.... + )} + + } + onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)} + /> + )} + + {/* */}
); }; @@ -372,9 +391,10 @@ class SchedulesPage extends React.Component { - // const { filters } = this.state; - // const { scheduleStore } = this.props.store; - // scheduleStore.updateItems(filters.searchTerm); + const { filters } = this.state; + const { store } = this.props; + const { scheduleStore } = store; + scheduleStore.updateItems(filters); }; debouncedUpdateSchedules = debounce(this.applyFilters, 1000);