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);