Merge pull request #780 from grafana/666-display-ical-and-terraform-schedules-in-list
666 display ical and terraform schedules in list
This commit is contained in:
commit
f04e72e498
5 changed files with 163 additions and 45 deletions
|
|
@ -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 = (
|
||||
<div>
|
||||
{item.warnings.map((warning: string, key: number) => (
|
||||
<p key={key}>{warning}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Tooltip placement="top" content={tooltipContent}>
|
||||
<Icon style={{ color: PENDING_COLOR }} name="exclamation-triangle" />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ScheduleWarning;
|
||||
|
|
@ -68,9 +68,8 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
|
|||
</Field>
|
||||
<Field label="Type">
|
||||
<RadioButtonGroup
|
||||
disabled
|
||||
options={[
|
||||
{ label: 'All', value: 'all' },
|
||||
{ label: 'All', value: undefined },
|
||||
{
|
||||
label: 'Web',
|
||||
value: ScheduleType.API,
|
||||
|
|
@ -84,7 +83,7 @@ const SchedulesFilters = (props: SchedulesFiltersProps) => {
|
|||
value: ScheduleType.Calendar,
|
||||
},
|
||||
]}
|
||||
value={value.type}
|
||||
value={value?.type}
|
||||
onChange={handleTypeChange}
|
||||
/>
|
||||
</Field>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { Rotation, RotationType, Schedule, ScheduleEvent, Shift, Event, Layer, S
|
|||
|
||||
export class ScheduleStore extends BaseStore {
|
||||
@observable
|
||||
searchResult: { [key: string]: Array<Schedule['id']> } = {};
|
||||
searchResult: { results?: Array<Schedule['id']> } = {};
|
||||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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<SchedulePageProps, SchedulePageState>
|
|||
shiftIdToShowOverridesForm: undefined,
|
||||
isLoading: true,
|
||||
showEditForm: false,
|
||||
showScheduleICalSettings: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +93,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
shiftIdToShowRotationForm,
|
||||
shiftIdToShowOverridesForm,
|
||||
showEditForm,
|
||||
showScheduleICalSettings,
|
||||
} = this.state;
|
||||
|
||||
const { scheduleStore, currentTimezone } = store;
|
||||
|
|
@ -109,6 +114,7 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
<Text.Title editable editModalTitle="Schedule name" level={2} onTextChange={this.handleNameChange}>
|
||||
{schedule?.name}
|
||||
</Text.Title>
|
||||
{schedule && <ScheduleWarning item={schedule} />}
|
||||
</HorizontalGroup>
|
||||
<HorizontalGroup spacing="lg">
|
||||
{users && (
|
||||
|
|
@ -118,6 +124,16 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
</HorizontalGroup>
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
{schedule?.type === ScheduleType.Ical && (
|
||||
<HorizontalGroup>
|
||||
<Button variant="secondary" onClick={this.handleExportClick()}>
|
||||
Export
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.handleReloadClick(scheduleId)}>
|
||||
Reload
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
)}
|
||||
<ToolbarButton
|
||||
icon="cog"
|
||||
tooltip="Settings"
|
||||
|
|
@ -206,6 +222,16 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
{showScheduleICalSettings && (
|
||||
<Modal
|
||||
isOpen
|
||||
title="Schedule export"
|
||||
closeOnEscape
|
||||
onDismiss={() => this.setState({ showScheduleICalSettings: false })}
|
||||
>
|
||||
<ScheduleICalSettings id={scheduleId} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -371,6 +397,47 @@ class SchedulePage extends React.Component<SchedulePageProps, SchedulePageState>
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -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<SchedulesPageProps, SchedulesPageSta
|
|||
const { store } = this.props;
|
||||
this.state = {
|
||||
startMoment: getStartOfWeek(store.currentTimezone),
|
||||
filters: { searchTerm: '', status: 'all', type: ScheduleType.API },
|
||||
filters: { searchTerm: '', status: 'all', type: undefined },
|
||||
showNewScheduleSelector: false,
|
||||
expandedRowKeys: [],
|
||||
scheduleIdToEdit: undefined,
|
||||
|
|
@ -80,10 +81,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
render: this.renderType,
|
||||
},
|
||||
{
|
||||
width: '10%',
|
||||
width: '5%',
|
||||
title: 'Status',
|
||||
key: 'name',
|
||||
render: this.renderStatus,
|
||||
render: (item: Schedule) => this.renderStatus(item),
|
||||
},
|
||||
{
|
||||
width: '30%',
|
||||
|
|
@ -107,6 +108,11 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
title: 'Slack user group',
|
||||
render: this.renderUserGroup,
|
||||
},
|
||||
{
|
||||
width: '5%',
|
||||
key: 'warning',
|
||||
render: this.renderWarning,
|
||||
},
|
||||
{
|
||||
width: '50px',
|
||||
key: 'buttons',
|
||||
|
|
@ -119,7 +125,6 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
|
||||
const data = schedules
|
||||
? schedules
|
||||
.filter((schedule) => schedule.type === ScheduleType.API)
|
||||
.filter(
|
||||
(schedule) =>
|
||||
filters.status === 'all' ||
|
||||
|
|
@ -265,38 +270,52 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
return typeToVerbal[value];
|
||||
};
|
||||
|
||||
renderWarning = (item: Schedule) => {
|
||||
return <ScheduleWarning item={item} />;
|
||||
};
|
||||
|
||||
renderStatus = (item: Schedule) => {
|
||||
const {
|
||||
store: { scheduleStore },
|
||||
} = this.props;
|
||||
|
||||
const relatedEscalationChains = scheduleStore.relatedEscalationChains[item.id];
|
||||
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<ScheduleCounter
|
||||
type="link"
|
||||
count={item.number_of_escalation_chains}
|
||||
tooltipTitle="Used in escalations"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="sm">
|
||||
{relatedEscalationChains ? (
|
||||
relatedEscalationChains.length ? (
|
||||
relatedEscalationChains.map((escalationChain) => (
|
||||
<PluginLink key={escalationChain.pk} query={{ page: 'escalations', id: escalationChain.pk }}>
|
||||
{escalationChain.name}
|
||||
</PluginLink>
|
||||
))
|
||||
{item.number_of_escalation_chains > 0 && (
|
||||
<ScheduleCounter
|
||||
type="link"
|
||||
count={item.number_of_escalation_chains}
|
||||
tooltipTitle="Used in escalations"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="sm">
|
||||
{relatedEscalationChains ? (
|
||||
relatedEscalationChains.length ? (
|
||||
relatedEscalationChains.map((escalationChain) => (
|
||||
<div key={escalationChain.pk}>
|
||||
<PluginLink query={{ page: 'escalations', id: escalationChain.pk }}>
|
||||
{escalationChain.name}
|
||||
</PluginLink>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
'Not used yet'
|
||||
)
|
||||
) : (
|
||||
'Not used yet'
|
||||
)
|
||||
) : (
|
||||
<LoadingPlaceholder>Loading related escalation chains....</LoadingPlaceholder>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
}
|
||||
onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)}
|
||||
/>
|
||||
<LoadingPlaceholder>Loading related escalation chains....</LoadingPlaceholder>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
}
|
||||
onHover={this.getUpdateRelatedEscalationChainsHandler(item.id)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* <ScheduleCounter
|
||||
type="warning"
|
||||
count={warningsCount}
|
||||
tooltipTitle="Warnings"
|
||||
tooltipContent="Schedule has unassigned time periods during next 7 days"
|
||||
/>*/}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -372,9 +391,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
};
|
||||
|
||||
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;
|
||||
scheduleStore.updateItems(filters);
|
||||
};
|
||||
|
||||
debouncedUpdateSchedules = debounce(this.applyFilters, 1000);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue