Skip to content

Commit

Permalink
[SDCP-409] [Client] Scheduled exports of search filters (#1519)
Browse files Browse the repository at this point in the history
* New and updated editor, list and preview fields

* Add scheduling to EventsPlanningFilters modal

* e2e tests

* Use gettext in delete filter/schedule modals

* Localize fields for week_days, hour and month_day
  • Loading branch information
MarkLark86 authored Jan 20, 2021
1 parent a2f864e commit 0899075
Show file tree
Hide file tree
Showing 32 changed files with 1,486 additions and 81 deletions.
2 changes: 2 additions & 0 deletions client/components/EventsPlanningFilters/EditFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export class EditFilter extends React.Component<IEventsPlanningContentPanelProps
key="save"
onClick={this.onSaveHandler}
disabled={pristine || invalid}
data-test-id="manage-filters--save-filter"
>
{this.props.filter?._id == null ?
gettext('Create') :
Expand Down Expand Up @@ -213,6 +214,7 @@ export class EditFilter extends React.Component<IEventsPlanningContentPanelProps
label: gettext('Filter Name'),
autoFocus: true,
required: true,
testId: 'field-filter_name',
},
item_type: {
onChange: this.onTypeChanged,
Expand Down
220 changes: 220 additions & 0 deletions client/components/EventsPlanningFilters/EditFilterSchedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import React from 'react';
import {connect} from 'react-redux';
import {cloneDeep} from 'lodash';

import {superdeskApi} from '../../superdeskApi';
import {IEventsPlanningContentPanelProps, ISearchFilterSchedule, SCHEDULE_FREQUENCY, WEEK_DAY} from '../../interfaces';

import {SidePanel, ToggleBox} from '../UI';
import {renderFieldsForPanel} from '../fields';
import {IDesk} from 'superdesk-api';
import {desks as getDesks} from '../../selectors/general';

interface IProps extends IEventsPlanningContentPanelProps {
desks: Array<IDesk>;
}

interface IState {
pristine: boolean;
schedule: Partial<ISearchFilterSchedule>;
invalid: boolean;
errors: {[key: string]: string};
}

const mapStateToProps = (state) => ({
desks: getDesks(state),
});

export class EditFilterScheduleComponent extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
pristine: false,
schedule: cloneDeep(this.props.filter.schedules?.[0] ?? {
frequency: SCHEDULE_FREQUENCY.HOURLY,
desk: this.props.desks[0]._id,
}),
invalid: false,
errors: {},
};

this.onSaveHandler = this.onSaveHandler.bind(this);
this.previewFilter = this.previewFilter.bind(this);
this.onChange = this.onChange.bind(this);
}

onSaveHandler() {
const schedule = Object.assign({}, this.state.schedule);

switch (schedule.frequency) {
case SCHEDULE_FREQUENCY.HOURLY:
delete schedule.hour;
delete schedule.day;
delete schedule.week_days;
break;
case SCHEDULE_FREQUENCY.WEEKLY:
delete schedule.day;
if (!schedule.week_days?.length) {
schedule.week_days = [
WEEK_DAY.SUNDAY,
WEEK_DAY.MONDAY,
WEEK_DAY.TUESDAY,
WEEK_DAY.WEDNESDAY,
WEEK_DAY.THURSDAY,
WEEK_DAY.FRIDAY,
WEEK_DAY.SATURDAY,
];
}
break;
case SCHEDULE_FREQUENCY.MONTHLY:
delete schedule.week_days;
break;
}

this.props.onSave({
...this.props.filter,
schedules: [schedule],
}).then(() => this.props.onClose());
}

previewFilter() {
this.props.previewFilter(this.props.filter);
}

onChange<T extends keyof ISearchFilterSchedule>(field: T, value: ISearchFilterSchedule[T]) {
const schedule = {...this.state.schedule};

schedule[field] = value;

if (field === 'frequency' && value === 'weekly') {
// When changing the frequency to weekly
// If the week_days array is empty, delete the attribute
// This enabled the input field to automatically select all days
// Which converts this to a 'daily' schedule automatically
if (!schedule.week_days?.length) {
delete schedule.week_days;
}
}

this.setState({schedule});
}

getScheduleProfile() {
const profile = {
frequency: {enabled: true, index: 1},
week_days: {enabled: false, index: 2},
month_day: {enabled: false, index: 3},
hour: {enabled: false, index: 4},
};

switch (this.state.schedule.frequency) {
case SCHEDULE_FREQUENCY.HOURLY:
break;
case SCHEDULE_FREQUENCY.WEEKLY:
profile.week_days.enabled = true;
profile.hour.enabled = true;
break;
case SCHEDULE_FREQUENCY.MONTHLY:
profile.month_day.enabled = true;
profile.hour.enabled = true;
break;
}

return profile;
}

render() {
const {gettext} = superdeskApi.localization;
const scheduleProfile = this.getScheduleProfile();

return (
<React.Fragment>
<SidePanel.Header className="side-panel__header--border-b">
<div className="subnav__sliding-toolbar">
<h3 className="side-panel__heading">
{gettext('Filter Schedule')}
</h3>
<div className="button-group button-group--right">
<button
className="btn"
key="cancel"
onClick={this.previewFilter}
>
{gettext('Cancel')}
</button>
<button
className="btn btn--primary"
key="save"
onClick={this.onSaveHandler}
disabled={this.state.pristine || this.state.invalid}
data-test-id="manage-filters--save-schedule"
>
{this.props.filter?._id == null ?
gettext('Create') :
gettext('Save')
}
</button>
</div>
</div>
</SidePanel.Header>
<SidePanel.Content>
<SidePanel.ContentBlock flex={true}>
<SidePanel.ContentBlockInner grow={true}>
{renderFieldsForPanel(
'editor',
scheduleProfile,
{
item: this.state.schedule,
onChange: this.onChange,
},
{
week_days: {
defaultAllOn: true,
},
month_day: {
field: 'day',
},
}
)}

<ToggleBox
title={gettext('Destination')}
isOpen={true}
style="toggle-box--circle"
noMargin={true}
>
{renderFieldsForPanel(
'editor',
{
desk: {enabled: true, index: 1},
content_template: {enabled: true, index: 2},
export_template: {enabled: true, index: 3},
},
{
item: this.state.schedule,
onChange: this.onChange,
},
{
content_template: {
field: 'article_template',
label: gettext('Article Template'),
desk: this.state.schedule.desk,
},
export_template: {
field: 'template',
label: gettext('Custom Layout'),
itemType: this.props.filter.item_type,
clearable: true,
},
},
)}
</ToggleBox>
</SidePanel.ContentBlockInner>
</SidePanel.ContentBlock>
</SidePanel.Content>
</React.Fragment>
);
}
}

export const EditFilterSchedule = connect(mapStateToProps)(EditFilterScheduleComponent);
76 changes: 55 additions & 21 deletions client/components/EventsPlanningFilters/FilterItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import moment from 'moment';

import {PRIVILEGES} from '../../constants';
import {List} from '../UI';
import {ItemActionsMenu} from '../ItemActionsMenu';
import {ISearchFilter, ICalendar, IAgenda} from '../../interfaces';
import {superdeskApi} from '../../superdeskApi';
import {renderFieldsForPanel} from '../fields';
Expand All @@ -15,6 +16,8 @@ interface IProps {
calendars: Array<ICalendar>;
agendas: Array<IAgenda>;
previewFilter(filter: ISearchFilter): void;
editFilterSchedule(filter: ISearchFilter): void;
deleteFilterSchedule(filter: ISearchFilter): void;
}

export class FilterItem extends React.PureComponent<IProps> {
Expand All @@ -24,23 +27,52 @@ export class FilterItem extends React.PureComponent<IProps> {
this.previewFilter = this.previewFilter.bind(this);
this.editFilter = this.editFilter.bind(this);
this.deleteFilter = this.deleteFilter.bind(this);
this.editFilterSchedule = this.editFilterSchedule.bind(this);
}

previewFilter() {
this.props.previewFilter(this.props.filter);
}

editFilter(event) {
event.stopPropagation();
editFilter() {
this.props.editFilter(this.props.filter);
}

deleteFilter() {
this.props.deleteFilter(this.props.filter);
}

editFilterSchedule() {
this.props.editFilterSchedule(this.props.filter);
}

render() {
const {gettext} = superdeskApi.localization;
const actions = !this.props.privileges[PRIVILEGES.EVENTS_PLANNING_FILTERS_MANAGEMENT] ?
[] :
[{
icon: 'icon-pencil',
label: gettext('Edit Filter'),
callback: this.editFilter,
}, {
icon: 'icon-trash',
label: gettext('Delete'),
callback: this.deleteFilter,
}, {
icon: 'icon-time',
label: !this.props.filter.schedules?.length ?
gettext('Create Scheduled Export') :
gettext('Edit Scheduled Export'),
callback: this.editFilterSchedule,
}];

if (actions.length && this.props.filter.schedules?.length) {
actions.push({
icon: 'icon-trash',
label: gettext('Delete Scheduled Export'),
callback: () => this.props.deleteFilterSchedule(this.props.filter),
});
}

return (
<List.Item shadow={1} onClick={this.previewFilter}>
Expand Down Expand Up @@ -89,27 +121,29 @@ export class FilterItem extends React.PureComponent<IProps> {
{}
)}
</List.Row>
{!this.props.filter.schedules?.length ? null : (
<List.Row>
{renderFieldsForPanel(
'list',
{
filter_schedule: {enabled: true, index: 1},
},
{
item: this.props.filter,
},
{
filter_schedule: {
editSchedule: this.props.editFilterSchedule,
deleteSchedule: this.props.deleteFilterSchedule,
},
}
)}
</List.Row>
)}
</List.Column>
{!!this.props.privileges[PRIVILEGES.EVENTS_PLANNING_FILTERS_MANAGEMENT] && (
{!actions.length ? null : (
<List.ActionMenu>
{this.props.editFilter && (
<button
onClick={this.editFilter}
className="dropdown__toggle"
data-sd-tooltip={gettext('Edit Filter')}
data-flow="left"
>
<i className="icon-pencil" />
</button>
)}
<button
onClick={this.deleteFilter}
className="dropdown__toggle"
data-sd-tooltip={gettext('Delete Filter')}
data-flow="left"
>
<i className="icon-trash" />
</button>
<ItemActionsMenu actions={actions} />
</List.ActionMenu>
)}
</List.Item>
Expand Down
6 changes: 6 additions & 0 deletions client/components/EventsPlanningFilters/FiltersList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface IProps {
editFilter(filter: ISearchFilter): void;
deleteFilter(filter: ISearchFilter): void;
previewFilter(filter: ISearchFilter): void;
editFilterSchedule(filter: ISearchFilter): void;
deleteFilterSchedule(filter: ISearchFilter): void;

filters: Array<ISearchFilter>;
calendars: Array<ICalendar>;
Expand All @@ -39,6 +41,8 @@ export class FiltersListComponent extends React.PureComponent<IProps> {
calendars,
agendas,
previewFilter,
editFilterSchedule,
deleteFilterSchedule,
} = this.props;

return (
Expand All @@ -56,6 +60,8 @@ export class FiltersListComponent extends React.PureComponent<IProps> {
calendars={calendars}
agendas={agendas}
previewFilter={previewFilter}
editFilterSchedule={editFilterSchedule}
deleteFilterSchedule={deleteFilterSchedule}
/>
))
)}
Expand Down
Loading

0 comments on commit 0899075

Please sign in to comment.