From 46990b51b32366b59c5bfa7fd7f28dcd48e58ce7 Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Mon, 24 Oct 2022 14:34:27 +0200 Subject: [PATCH 1/6] Schedules polishing: count of esc chains deleted when it is 0, warning column has been added --- .../src/pages/schedules_NEW/Schedules.tsx | 86 +++++++++++++------ 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index 8c7ed34a..06c94d78 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -1,7 +1,16 @@ import React, { SyntheticEvent } from 'react'; import { getLocationSrv } from '@grafana/runtime'; -import { Button, HorizontalGroup, IconButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; +import { + Button, + HorizontalGroup, + IconButton, + LoadingPlaceholder, + VerticalGroup, + PENDING_COLOR, + Tooltip, + Icon, +} from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { debounce } from 'lodash-es'; @@ -82,7 +91,7 @@ class SchedulesPage extends React.Component { + if (item.warnings.length > 0) { + const tooltipContent = ( +
+ {item.warnings.map((warning: string) => ( +

{warning}

+ ))} +
+ ); + return ( + + + + ); + } + + return null; + }; + renderStatus = (item: Schedule) => { const { store: { scheduleStore }, } = this.props; const relatedEscalationChains = scheduleStore.relatedEscalationChains[item.id]; - + console.log('esc chains', item.number_of_escalation_chains); 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)} + /> + )} + {/* Date: Tue, 1 Nov 2022 10:39:36 +0100 Subject: [PATCH 2/6] Filtering by type has been added --- .../SchedulesFilters_NEW/SchedulesFilters.tsx | 3 +- .../src/models/schedule/schedule.ts | 19 ++++++----- .../src/models/schedule/schedule.types.ts | 1 + .../src/pages/schedule/Schedule.tsx | 33 +++++++++++++++++-- .../src/pages/schedules_NEW/Schedules.tsx | 13 +++++--- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx index dcb30d78..f8f3dc12 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) => { } = {}; + 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/models/schedule/schedule.types.ts b/grafana-plugin/src/models/schedule/schedule.types.ts index ffbec8dc..6a59b622 100644 --- a/grafana-plugin/src/models/schedule/schedule.types.ts +++ b/grafana-plugin/src/models/schedule/schedule.types.ts @@ -7,6 +7,7 @@ export enum ScheduleType { 'Calendar', 'Ical', 'API', + 'all' } export interface Schedule { diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index b49cb1bf..ed153fe3 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { SyntheticEvent } from 'react'; import { AppRootProps } from '@grafana/data'; import { getLocationSrv } from '@grafana/runtime'; @@ -16,7 +16,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 { Schedule, Shift } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; import { WithStoreProps } from 'state/types'; import { withMobXProviderContext } from 'state/withStore'; @@ -118,6 +118,16 @@ class SchedulePage extends React.Component )} + {schedule?.type === 1 && ( + + + + + )} this.setState({ startMoment: startMoment.add(7, 'day') }, this.handleDateRangeUpdate); }; + handleExportClick = () => { + console.log('EXPORT'); + }; + + handleReloadClick = (scheduleId: Schedule['id']) => { + const { store } = this.props; + + const { scheduleStore } = store; + + return async (event: SyntheticEvent) => { + event.stopPropagation(); + + await scheduleStore.reloadIcal(scheduleId); + + scheduleStore.updateItem(scheduleId); + this.updateEventsFor(scheduleId); + }; + }; + handleDelete = () => { const { store, diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index a2d48751..19c744eb 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -60,7 +60,7 @@ class SchedulesPage extends React.Component schedule.type === ScheduleType.API) .filter( (schedule) => filters.status === 'all' || @@ -145,6 +144,7 @@ class SchedulesPage extends React.Component + {console.log('DATA', data)}
@@ -410,13 +410,16 @@ class SchedulesPage extends React.Component { + console.log('filters1', filters); this.setState({ filters }, this.debouncedUpdateSchedules); }; applyFilters = () => { - // const { filters } = this.state; - // const { scheduleStore } = this.props.store; - // scheduleStore.updateItems(filters.searchTerm); + const { filters } = this.state; + const { store } = this.props; + const { scheduleStore } = store; + console.log('APPLY FILTERS'); + scheduleStore.updateItems(filters); }; debouncedUpdateSchedules = debounce(this.applyFilters, 1000); From efcc6364820592eb3366df6b1f24599bef74e20b Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Wed, 2 Nov 2022 12:43:25 +0100 Subject: [PATCH 3/6] Export and Reload buttons have been added to iCal schedule --- .../src/pages/schedule/Schedule.tsx | 53 ++++++++++++++++--- .../src/pages/schedules_NEW/Schedules.tsx | 16 +++--- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index ed153fe3..af848d0a 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -2,9 +2,11 @@ import React, { SyntheticEvent } 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 timezone from 'dayjs/plugin/timezone'; +import { omit } from 'lodash-es'; import { observer } from 'mobx-react'; import PluginLink from 'components/PluginLink/PluginLink'; @@ -15,6 +17,7 @@ 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 { Schedule, Shift } from 'models/schedule/schedule.types'; import { Timezone } from 'models/timezone/timezone.types'; @@ -24,7 +27,7 @@ import { withMobXProviderContext } from 'state/withStore'; import { getStartOfWeek } from './Schedule.helpers'; import styles from './Schedule.module.css'; - +dayjs.extend(timezone); const cx = cn.bind(styles); interface SchedulePageProps extends AppRootProps, WithStoreProps {} @@ -37,6 +40,7 @@ interface SchedulePageState { shiftIdToShowOverridesForm?: Shift['id']; isLoading: boolean; showEditForm: boolean; + scheduleIdToExport: Schedule['id']; } @observer @@ -53,6 +57,7 @@ class SchedulePage extends React.Component shiftIdToShowOverridesForm: undefined, isLoading: true, showEditForm: false, + scheduleIdToExport: undefined, }; } @@ -89,6 +94,7 @@ class SchedulePage extends React.Component shiftIdToShowRotationForm, shiftIdToShowOverridesForm, showEditForm, + scheduleIdToExport, } = this.state; const { scheduleStore, currentTimezone } = store; @@ -120,10 +126,10 @@ class SchedulePage extends React.Component {schedule?.type === 1 && ( - - @@ -216,6 +222,16 @@ class SchedulePage extends React.Component }} /> )} + {scheduleIdToExport && ( + this.setState({ scheduleIdToExport: undefined })} + > + + + )} ); } @@ -381,8 +397,11 @@ class SchedulePage extends React.Component this.setState({ startMoment: startMoment.add(7, 'day') }, this.handleDateRangeUpdate); }; - handleExportClick = () => { - console.log('EXPORT'); + handleExportClick = (scheduleId: Schedule['id']) => { + return (event: SyntheticEvent) => { + event.stopPropagation(); + this.setState({ scheduleIdToExport: scheduleId }); + }; }; handleReloadClick = (scheduleId: Schedule['id']) => { @@ -400,6 +419,28 @@ class SchedulePage extends React.Component }; }; + 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 19c744eb..6eeef357 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -144,7 +144,6 @@ class SchedulesPage extends React.Component - {console.log('DATA', data)}
@@ -283,8 +282,8 @@ class SchedulesPage extends React.Component 0) { const tooltipContent = (
- {item.warnings.map((warning: string) => ( -

{warning}

+ {item.warnings.map((warning: string, key: number) => ( +

{warning}

))}
); @@ -304,7 +303,6 @@ class SchedulesPage extends React.Component {item.number_of_escalation_chains > 0 && ( @@ -317,9 +315,11 @@ class SchedulesPage extends React.Component ( - - {escalationChain.name} - +
+ + {escalationChain.name} + +
)) ) : ( 'Not used yet' @@ -410,7 +410,6 @@ class SchedulesPage extends React.Component { - console.log('filters1', filters); this.setState({ filters }, this.debouncedUpdateSchedules); }; @@ -418,7 +417,6 @@ class SchedulesPage extends React.Component Date: Fri, 4 Nov 2022 11:46:20 +0100 Subject: [PATCH 4/6] escalation chains list fix, warning component has been added --- .../ScheduleWarning/ScheduleWarning.tsx | 31 +++++++++++++++++++ .../src/pages/schedule/Schedule.tsx | 2 ++ .../src/pages/schedules_NEW/Schedules.tsx | 31 +++---------------- 3 files changed, 37 insertions(+), 27 deletions(-) create mode 100644 grafana-plugin/src/components/ScheduleWarning/ScheduleWarning.tsx 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/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index af848d0a..d5da2d7f 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -10,6 +10,7 @@ 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'; @@ -115,6 +116,7 @@ class SchedulePage extends React.Component {schedule?.name} + {schedule && }
{users && ( diff --git a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx index 6eeef357..8127af67 100644 --- a/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx +++ b/grafana-plugin/src/pages/schedules_NEW/Schedules.tsx @@ -1,16 +1,7 @@ import React, { SyntheticEvent } from 'react'; import { getLocationSrv } from '@grafana/runtime'; -import { - Button, - HorizontalGroup, - IconButton, - LoadingPlaceholder, - VerticalGroup, - PENDING_COLOR, - Tooltip, - Icon, -} from '@grafana/ui'; +import { Button, HorizontalGroup, IconButton, LoadingPlaceholder, VerticalGroup } from '@grafana/ui'; import cn from 'classnames/bind'; import dayjs from 'dayjs'; import { debounce } from 'lodash-es'; @@ -20,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'; @@ -92,7 +84,7 @@ class SchedulesPage extends React.Component this.renderStatus(item), }, { width: '30%', @@ -279,22 +271,7 @@ class SchedulesPage extends React.Component { - if (item.warnings.length > 0) { - const tooltipContent = ( -
- {item.warnings.map((warning: string, key: number) => ( -

{warning}

- ))} -
- ); - return ( - - - - ); - } - - return null; + return ; }; renderStatus = (item: Schedule) => { From a05962cf8eb5699dae11edb2453ddc83bd0a9c3b Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Fri, 4 Nov 2022 14:43:50 +0100 Subject: [PATCH 5/6] All deleted from ScheduleType, show Export Ical link modal was changed to boolean --- .../SchedulesFilters_NEW/SchedulesFilters.tsx | 4 +-- .../src/models/schedule/schedule.types.ts | 1 - .../src/pages/schedule/Schedule.tsx | 31 +++++++++---------- .../src/pages/schedules_NEW/Schedules.tsx | 2 +- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx index f8f3dc12..b1fa8834 100644 --- a/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx +++ b/grafana-plugin/src/components/SchedulesFilters_NEW/SchedulesFilters.tsx @@ -69,7 +69,7 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => { { value: ScheduleType.Calendar, }, ]} - value={value.type} + value={value?.type} onChange={handleTypeChange} /> diff --git a/grafana-plugin/src/models/schedule/schedule.types.ts b/grafana-plugin/src/models/schedule/schedule.types.ts index 6a59b622..ffbec8dc 100644 --- a/grafana-plugin/src/models/schedule/schedule.types.ts +++ b/grafana-plugin/src/models/schedule/schedule.types.ts @@ -7,7 +7,6 @@ export enum ScheduleType { 'Calendar', 'Ical', 'API', - 'all' } export interface Schedule { diff --git a/grafana-plugin/src/pages/schedule/Schedule.tsx b/grafana-plugin/src/pages/schedule/Schedule.tsx index d5da2d7f..533f9f1f 100644 --- a/grafana-plugin/src/pages/schedule/Schedule.tsx +++ b/grafana-plugin/src/pages/schedule/Schedule.tsx @@ -1,4 +1,4 @@ -import React, { SyntheticEvent } from 'react'; +import React from 'react'; import { AppRootProps } from '@grafana/data'; import { getLocationSrv } from '@grafana/runtime'; @@ -20,7 +20,7 @@ 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 { Schedule, 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'; @@ -41,7 +41,7 @@ interface SchedulePageState { shiftIdToShowOverridesForm?: Shift['id']; isLoading: boolean; showEditForm: boolean; - scheduleIdToExport: Schedule['id']; + showScheduleICalSettings: boolean; } @observer @@ -58,7 +58,7 @@ class SchedulePage extends React.Component shiftIdToShowOverridesForm: undefined, isLoading: true, showEditForm: false, - scheduleIdToExport: undefined, + showScheduleICalSettings: false, }; } @@ -95,7 +95,7 @@ class SchedulePage extends React.Component shiftIdToShowRotationForm, shiftIdToShowOverridesForm, showEditForm, - scheduleIdToExport, + showScheduleICalSettings, } = this.state; const { scheduleStore, currentTimezone } = store; @@ -126,9 +126,9 @@ class SchedulePage extends React.Component
)} - {schedule?.type === 1 && ( + {schedule?.type === ScheduleType.Ical && ( -