Work on sending campaigns. Campaign status page half-way done, but does not work yet.
This commit is contained in:
parent
67d7129f7b
commit
d1fa4f4211
66 changed files with 1653 additions and 525 deletions
|
@ -31,6 +31,7 @@ export default class API extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadAccessToken();
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ export default class Account extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadFormValues();
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ export default class Account extends Component {
|
|||
password2: ''
|
||||
});
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.validateResetToken();
|
||||
}
|
||||
|
||||
|
|
|
@ -45,12 +45,13 @@ import {getUrl} from "../lib/urls";
|
|||
import {
|
||||
campaignOverridables,
|
||||
CampaignSource,
|
||||
CampaignStatus,
|
||||
CampaignType
|
||||
} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {ResourceType} from "../lib/mosaico";
|
||||
import {getCampaignTypeLabels} from "./helpers";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
|
||||
@translate()
|
||||
@withForm
|
||||
|
@ -66,7 +67,8 @@ export default class CUD extends Component {
|
|||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
|
||||
this.mailerTypes = getMailerTypes(props.t);
|
||||
|
||||
this.campaignTypes = getCampaignTypeLabels(t);
|
||||
const { campaignTypeLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
|
||||
this.createTitles = {
|
||||
[CampaignType.REGULAR]: t('Create Regular Campaign'),
|
||||
|
@ -134,6 +136,7 @@ export default class CUD extends Component {
|
|||
|
||||
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||
newState.sendConfiguration = null;
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(sendConfigurationId);
|
||||
}
|
||||
|
||||
|
@ -167,7 +170,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_overriden'] = !!data[overridable + '_override'];
|
||||
data[overridable + '_overriden'] = data[overridable + '_override'] === null;
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
|
@ -184,9 +187,14 @@ export default class CUD extends Component {
|
|||
}
|
||||
data.lists = lsts;
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(data.send_configuration);
|
||||
});
|
||||
|
||||
if (this.props.entity.status === CampaignStatus.SENDING) {
|
||||
this.disableForm();
|
||||
}
|
||||
|
||||
} else {
|
||||
const data = {};
|
||||
for (const overridable of campaignOverridables) {
|
||||
|
@ -620,7 +628,7 @@ export default class CUD extends Component {
|
|||
const campaignsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypes[data] },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypeLabels[data] },
|
||||
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 5, title: t('Namespace') }
|
||||
];
|
||||
|
@ -671,6 +679,12 @@ export default class CUD extends Component {
|
|||
|
||||
<Title>{isEdit ? this.editTitles[this.getFormValue('type')] : this.createTitles[this.getFormValue('type')]}</Title>
|
||||
|
||||
{isEdit && this.props.entity.status === CampaignStatus.SENDING &&
|
||||
<div className={`alert alert-info`} role="alert">
|
||||
{t('Form cannot be edited because the campaign is currently being sent out. Wait till the sending is finished and refresh.')}
|
||||
</div>
|
||||
}
|
||||
|
||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||
<InputField id="name" label={t('Name')}/>
|
||||
<TextArea id="description" label={t('Description')}/>
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
CampaignType
|
||||
} from "../../../shared/campaigns";
|
||||
import {checkPermissions} from "../lib/permissions";
|
||||
import {getCampaignTypeLabels} from "./helpers";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
|
||||
@translate()
|
||||
@withPageHelpers
|
||||
|
@ -37,15 +37,9 @@ export default class List extends Component {
|
|||
|
||||
const t = props.t;
|
||||
|
||||
this.campaignStatuses = {
|
||||
[CampaignStatus.IDLE]: t('Idle'),
|
||||
[CampaignStatus.FINISHED]: t('Finished'),
|
||||
[CampaignStatus.PAUSED]: t('Paused'),
|
||||
[CampaignStatus.INACTIVE]: t('Inactive'),
|
||||
[CampaignStatus.ACTIVE]: t('Active')
|
||||
};
|
||||
|
||||
this.campaignTypes = getCampaignTypeLabels(t);
|
||||
const { campaignTypeLabels, campaignStatusLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
this.campaignStatusLabels = campaignStatusLabels;
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
@ -65,6 +59,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
@ -74,7 +69,7 @@ export default class List extends Component {
|
|||
const columns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypes[data] },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypeLabels[data] },
|
||||
{
|
||||
data: 4,
|
||||
title: t('Status'),
|
||||
|
@ -87,7 +82,7 @@ export default class List extends Component {
|
|||
return t('Sending');
|
||||
}
|
||||
} else {
|
||||
return this.campaignStatuses[data];
|
||||
return this.campaignStatusLabels[data];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -100,6 +95,13 @@ export default class List extends Component {
|
|||
const campaignType = data[3];
|
||||
const campaignSource = data[6];
|
||||
|
||||
if (perms.includes('viewStats')) {
|
||||
actions.push({
|
||||
label: <Icon icon="send" title={t('Status')}/>,
|
||||
link: `/campaigns/${data[0]}/status`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('edit')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('Edit')}/>,
|
||||
|
|
387
client/src/campaigns/Status.js
Normal file
387
client/src/campaigns/Status.js
Normal file
|
@ -0,0 +1,387 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {translate} from 'react-i18next';
|
||||
import {
|
||||
requiresAuthenticatedUser,
|
||||
Title,
|
||||
withPageHelpers
|
||||
} from '../lib/page';
|
||||
import {
|
||||
AlignedRow,
|
||||
ButtonRow,
|
||||
CheckBox,
|
||||
DatePicker,
|
||||
Form,
|
||||
InputField,
|
||||
TableSelect,
|
||||
withForm
|
||||
} from '../lib/form';
|
||||
import {
|
||||
withAsyncErrorHandler,
|
||||
withErrorHandling
|
||||
} from '../lib/error-handling';
|
||||
import {getCampaignLabels} from './helpers';
|
||||
import {Table} from "../lib/table";
|
||||
import {Button} from "../lib/bootstrap-components";
|
||||
import axios from "../lib/axios";
|
||||
import {getUrl} from "../lib/urls";
|
||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
import {CampaignStatus} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import campaignsStyles from "./styles.scss";
|
||||
|
||||
|
||||
@translate()
|
||||
@withForm
|
||||
@withPageHelpers
|
||||
@withErrorHandling
|
||||
@requiresAuthenticatedUser
|
||||
class TestUser extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
entity: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const t = this.props.t;
|
||||
|
||||
if (!state.getIn(['testUser', 'value'])) {
|
||||
state.setIn(['testUser', 'error'], t('Subscription has to be selected to show the campaign for a test user.'))
|
||||
} else {
|
||||
state.setIn(['testUser', 'error'], null);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.populateFormValues({
|
||||
testUser: null,
|
||||
});
|
||||
}
|
||||
|
||||
async previewAsync() {
|
||||
if (this.isFormWithoutErrors()) {
|
||||
const data = this.getFormValues();
|
||||
|
||||
// FIXME - navigate to campaign preview
|
||||
} else {
|
||||
this.showFormValidation();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const testUsersColumns = [
|
||||
{ data: 1, title: t('Email') },
|
||||
{ data: 4, title: t('List ID') },
|
||||
{ data: 5, title: t('List') },
|
||||
{ data: 6, title: t('Segment') },
|
||||
{ data: 7, title: t('List namespace') }
|
||||
];
|
||||
|
||||
return (
|
||||
<Form stateOwner={this}>
|
||||
<TableSelect id="testUser" label={t('Preview campaign as')} withHeader dropdown dataUrl={`rest/campaigns-test-users-table/${this.props.entity.id}`} columns={testUsersColumns} selectionLabelIndex={1} />
|
||||
<ButtonRow>
|
||||
<Button className="btn-primary" label={t('Preview')} onClickAsync={::this.previewAsync}/>
|
||||
</ButtonRow>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@translate()
|
||||
@withForm
|
||||
@withPageHelpers
|
||||
@withErrorHandling
|
||||
@requiresAuthenticatedUser
|
||||
class SendControls extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.initForm();
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
entity: PropTypes.object.isRequired,
|
||||
refreshEntity: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const t = this.props.t;
|
||||
|
||||
state.setIn(['date', 'error'], null);
|
||||
state.setIn(['time', 'error'], null);
|
||||
|
||||
if (!state.getIn(['sendLater', 'value'])) {
|
||||
const dateValue = state.getIn(['date', 'value']);
|
||||
if (!dateValue) {
|
||||
state.setIn(['date', 'error'], t('Date must not be empty'));
|
||||
} else if (!moment.utc(dateValue, 'YYYY-MM-DD').isValid()) {
|
||||
state.setIn(['date', 'error'], t('Date is invalid'));
|
||||
}
|
||||
|
||||
const timeValue = state.getIn(['time', 'value']);
|
||||
const time = moment.utc(timeValue, 'HH:mm').isValid();
|
||||
if (!timeValue) {
|
||||
state.setIn(['time', 'error'], t('Time must not be empty'));
|
||||
} else if (!time) {
|
||||
state.setIn(['time', 'error'], t('Time is invalid'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const entity = this.props.entity;
|
||||
|
||||
if (entity.scheduled) {
|
||||
const date = moment(entity.scheduled);
|
||||
this.populateFormValues({
|
||||
sendLater: true,
|
||||
date: date.utc().format('YYYY-MM-DD'),
|
||||
time: date.utc().format('HH:mm')
|
||||
});
|
||||
|
||||
} else {
|
||||
this.populateFormValues({
|
||||
sendLater: false,
|
||||
date: '',
|
||||
time: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async refreshEntity() {
|
||||
await this.props.refreshEntity();
|
||||
}
|
||||
|
||||
async postAndMaskStateError(url) {
|
||||
try {
|
||||
await axios.post(getUrl(url));
|
||||
} catch (err) {
|
||||
if (err instanceof interoperableErrors.InvalidStateError) {
|
||||
// Just mask the fact that it's not possible to start anything and refresh instead.
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async scheduleAsync() {
|
||||
if (this.isFormWithoutErrors()) {
|
||||
const data = this.getFormValues();
|
||||
const date = moment.utc(data.date);
|
||||
const time = moment.utc(date.time);
|
||||
|
||||
date.hour(time.hour());
|
||||
date.minute(time.minute());
|
||||
date.second(0);
|
||||
date.millisecond(0);
|
||||
|
||||
await this.postAndMaskStateError(`rest/campaign-start-at/${this.props.entity.id}/${date.toDate()}`);
|
||||
|
||||
} else {
|
||||
this.showFormValidation();
|
||||
}
|
||||
|
||||
await this.refreshEntity();
|
||||
}
|
||||
|
||||
async startAsync() {
|
||||
await this.postAndMaskStateError(`rest/campaign-start/${this.props.entity.id}`);
|
||||
await this.refreshEntity();
|
||||
}
|
||||
|
||||
async stopAsync() {
|
||||
await this.postAndMaskStateError(`rest/campaign-stop/${this.props.entity.id}`);
|
||||
await this.refreshEntity();
|
||||
}
|
||||
|
||||
async resetAsync() {
|
||||
await this.postAndMaskStateError(`rest/campaign-reset/${this.props.entity.id}`);
|
||||
await this.refreshEntity();
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
const entity = this.props.entity;
|
||||
|
||||
if (entity.status === CampaignStatus.IDLE || entity.status === CampaignStatus.PAUSED || (entity.status === CampaignStatus.SCHEDULED && entity.scheduled)) {
|
||||
|
||||
const subscrInfo = entity.subscriptionsTotal === undefined ? '' : ` (${entity.subscriptionsTotal} ${t('subscribers')})`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AlignedRow label={t('Send status')}>
|
||||
{entity.scheduled ? t('Campaign is scheduled for delivery.') : t('Campaign is ready to be sent out.')}
|
||||
</AlignedRow>
|
||||
|
||||
<Form stateOwner={this}>
|
||||
<CheckBox id="sendLater" label={t('Send later')} text={t('Schedule deliver at particular date/time')}/>
|
||||
{this.getFormValue('sendLater') &&
|
||||
<div>
|
||||
<DatePicker id="date" label={t('Date')} />
|
||||
<InputField id="time" label={t('Time')} help={t('Enter 24-hour time in format HH:MM (e.g. 13:48)')}/>
|
||||
</div>
|
||||
}
|
||||
</Form>
|
||||
<ButtonRow className={campaignsStyles.sendButtonRow}>
|
||||
{this.getFormValue('sendLater') ?
|
||||
<Button className="btn-primary" icon="send" label={(entity.scheduled ? t('Reschedule send') : t('Schedule send')) + subscrInfo} onClickAsync={::this.scheduleAsync}/>
|
||||
:
|
||||
<Button className="btn-primary" icon="send" label={t('Send') + subscrInfo} onClickAsync={::this.startAsync}/>
|
||||
}
|
||||
</ButtonRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (entity.status === CampaignStatus.SENDING || (entity.status === CampaignStatus.SCHEDULED && !entity.scheduled)) {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{t('Campaign is being sent out.')}
|
||||
</div>
|
||||
<ButtonRow>
|
||||
<Button className="btn-primary" icon="stop" label={t('Stop')} onClickAsync={::this.pauseAsync}/>
|
||||
</ButtonRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
} else if (entity.status === CampaignStatus.FINISHED) {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{t('All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers.')}
|
||||
</div>
|
||||
<ButtonRow>
|
||||
<Button className="btn-primary" icon="play" label={t('Continue')} onClickAsync={::this.startAsync}/>
|
||||
<Button className="btn-primary" icon="refresh" label={t('Reset')} onClickAsync={::this.resetAsync}/>
|
||||
</ButtonRow>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@translate()
|
||||
@withPageHelpers
|
||||
@withErrorHandling
|
||||
@requiresAuthenticatedUser
|
||||
export default class Status extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.state = {
|
||||
entity: props.entity,
|
||||
sendConfiguration: null
|
||||
};
|
||||
|
||||
const { campaignTypeLabels, campaignStatusLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
this.campaignStatusLabels = campaignStatusLabels;
|
||||
|
||||
this.refreshTimeoutHandler = ::this.periodicRefreshTask;
|
||||
this.refreshTimeoutId = 0;
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
entity: PropTypes.object
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async refreshEntity() {
|
||||
let resp;
|
||||
|
||||
resp = await axios.get(getUrl(`rest/campaigns-stats/${this.props.entity.id}`));
|
||||
const entity = resp.data;
|
||||
|
||||
resp = await axios.get(getUrl(`rest/send-configurations-public/${entity.send_configuration}`));
|
||||
const sendConfiguration = resp.data;
|
||||
|
||||
this.setState({
|
||||
entity,
|
||||
sendConfiguration
|
||||
});
|
||||
}
|
||||
|
||||
async periodicRefreshTask() {
|
||||
// The periodic task runs all the time, so that we don't have to worry about starting/stopping it as a reaction to the buttons.
|
||||
await this.refreshEntity();
|
||||
if (this.refreshTimeoutHandler) { // For some reason the task gets rescheduled if server is restarted while the page is shown. That why we have this check here.
|
||||
this.refreshTimeoutId = setTimeout(this.refreshTimeoutHandler, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.periodicRefreshTask();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.refreshTimeoutId);
|
||||
this.refreshTimeoutHandler = null;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
const entity = this.state.entity;
|
||||
|
||||
let sendSettings;
|
||||
if (this.state.sendConfiguration) {
|
||||
sendSettings = [];
|
||||
|
||||
const addOverridable = (id, label) => {
|
||||
sendSettings.push(<AlignedRow key={id} label={label}>{entity[id + '_override'] === null ? this.state.sendConfiguration[id] : entity[id + '_override']}</AlignedRow>);
|
||||
};
|
||||
|
||||
addOverridable('from_name', t('"From" name'));
|
||||
addOverridable('from_email', t('"From" email address'));
|
||||
addOverridable('reply_to', t('"Reply-to" email address'));
|
||||
addOverridable('subject', t('"Subject" line'));
|
||||
} else {
|
||||
sendSettings = <AlignedRow>{t('Loading send configuration ...')}</AlignedRow>
|
||||
}
|
||||
|
||||
const listsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('Namespace') },
|
||||
{ data: 4, title: t('Segment') }
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>{t('Campaign Status')}</Title>
|
||||
|
||||
<AlignedRow label={t('Name')}>{entity.name}</AlignedRow>
|
||||
<AlignedRow label={t('Subscribers')}>{entity.subscriptionsTotal === undefined ? t('computing ...') : entity.subscriptionsTotal}</AlignedRow>
|
||||
<AlignedRow label={t('Status')}>{this.campaignStatusLabels[entity.status]}</AlignedRow>
|
||||
|
||||
{sendSettings}
|
||||
|
||||
<AlignedRow label={t('Target lists/segments')}>
|
||||
<Table withHeader dataUrl={`rest/lists-with-segment-by-campaign-table/${this.props.entity.id}`} columns={listsColumns} />
|
||||
</AlignedRow>
|
||||
|
||||
<hr/>
|
||||
|
||||
<TestUser entity={entity}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<SendControls entity={entity} refreshEntity={::this.refreshEntity}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
import {CampaignType} from "../../../shared/campaigns";
|
||||
import {
|
||||
CampaignStatus,
|
||||
CampaignType
|
||||
} from "../../../shared/campaigns";
|
||||
|
||||
export function getCampaignTypeLabels(t) {
|
||||
export function getCampaignLabels(t) {
|
||||
|
||||
const campaignTypeLabels = {
|
||||
[CampaignType.REGULAR]: t('Regular'),
|
||||
|
@ -10,5 +13,19 @@ export function getCampaignTypeLabels(t) {
|
|||
[CampaignType.RSS]: t('RSS')
|
||||
};
|
||||
|
||||
return campaignTypeLabels;
|
||||
const campaignStatusLabels = {
|
||||
[CampaignStatus.IDLE]: t('Idle'),
|
||||
[CampaignStatus.SCHEDULED]: t('Scheduled'),
|
||||
[CampaignStatus.PAUSED]: t('Paused'),
|
||||
[CampaignStatus.FINISHED]: t('Finished'),
|
||||
[CampaignStatus.PAUSED]: t('Paused'),
|
||||
[CampaignStatus.INACTIVE]: t('Inactive'),
|
||||
[CampaignStatus.ACTIVE]: t('Active')
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
campaignStatusLabels,
|
||||
campaignTypeLabels
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import Status from './Status';
|
||||
import CampaignsCUD from './CUD';
|
||||
import Content from './Content';
|
||||
import CampaignsList from './List';
|
||||
|
@ -29,6 +30,12 @@ function getMenus(t) {
|
|||
},
|
||||
link: params => `/campaigns/${params.campaignId}/edit`,
|
||||
navs: {
|
||||
status: {
|
||||
title: t('Status'),
|
||||
link: params => `/campaigns/${params.campaignId}/status`,
|
||||
visible: resolved => resolved.campaign.permissions.includes('viewStats'),
|
||||
panelRender: props => <Status entity={props.resolved.campaign} />
|
||||
},
|
||||
':action(edit|delete)': {
|
||||
title: t('Edit'),
|
||||
link: params => `/campaigns/${params.campaignId}/edit`,
|
||||
|
|
|
@ -32,7 +32,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.newEntry{
|
||||
.newEntry {
|
||||
text-align: right;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.sendButtonRow {
|
||||
margin-top: 10px;
|
||||
}
|
|
@ -30,7 +30,7 @@ import {
|
|||
Event
|
||||
} from '../../../../shared/triggers';
|
||||
import moment from 'moment';
|
||||
import {getCampaignTypeLabels} from "../helpers";
|
||||
import {getCampaignLabels} from "../helpers";
|
||||
|
||||
|
||||
@translate()
|
||||
|
@ -44,7 +44,7 @@ export default class CUD extends Component {
|
|||
|
||||
this.state = {};
|
||||
|
||||
this.campaignTypes = getCampaignTypeLabels(props.t);
|
||||
this.campaignTypeLabels = getCampaignLabels(props.t);
|
||||
|
||||
const {entityLabels, eventLabels} = getTriggerTypes(props.t);
|
||||
this.entityLabels = entityLabels;
|
||||
|
@ -186,7 +186,7 @@ export default class CUD extends Component {
|
|||
const campaignsColumns = [
|
||||
{ data: 1, title: t('Name') },
|
||||
{ data: 2, title: t('Description') },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypes[data] },
|
||||
{ data: 3, title: t('Type'), render: data => this.campaignTypeLabels[data] },
|
||||
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
|
||||
{ data: 5, title: t('Namespace') }
|
||||
];
|
||||
|
|
1
client/src/lib/bootstrap-components.js
vendored
1
client/src/lib/bootstrap-components.js
vendored
|
@ -249,6 +249,7 @@ class ModalDialog extends Component {
|
|||
// are capture, converted to onClose callback and prevented. It's up to the parent to decide whether to
|
||||
// hide the modal or not.
|
||||
if (!this.props.hidden) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onClose();
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
|
|
@ -185,6 +185,7 @@ class RouteContent extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
|
@ -193,6 +194,7 @@ class RouteContent extends Component {
|
|||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.match.params !== nextProps.match.params && needsResolve(this.props.route, nextProps.route, this.props.match, nextProps.match)) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.resolve(nextProps);
|
||||
}
|
||||
}
|
||||
|
@ -279,6 +281,7 @@ class SectionContent extends Component {
|
|||
}
|
||||
|
||||
this.historyUnlisten = props.history.listen((location, action) => {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.closeFlashMessage();
|
||||
})
|
||||
}
|
||||
|
|
|
@ -154,6 +154,7 @@ class Table extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.notifySelection(this.props.onSelectionDataAsync, this.selectionMap);
|
||||
}
|
||||
}
|
||||
|
@ -278,6 +279,7 @@ class Table extends Component {
|
|||
|
||||
if (self.props.selectMode === TableSelectMode.SINGLE) {
|
||||
if (selectionMap.size !== 1 || !selectionMap.has(rowKey)) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
self.notifySelection(self.props.onSelectionChangedAsync, new Map([[rowKey, data]]));
|
||||
}
|
||||
|
||||
|
@ -290,6 +292,7 @@ class Table extends Component {
|
|||
newSelMap.set(rowKey, data);
|
||||
}
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
self.notifySelection(self.props.onSelectionChangedAsync, newSelMap);
|
||||
}
|
||||
});
|
||||
|
@ -321,6 +324,7 @@ class Table extends Component {
|
|||
clearTimeout(this.refreshTimeoutId);
|
||||
});
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchAndNotifySelectionData();
|
||||
}
|
||||
|
||||
|
@ -344,6 +348,8 @@ class Table extends Component {
|
|||
});
|
||||
|
||||
this.updateSelectInfo();
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchAndNotifySelectionData();
|
||||
}
|
||||
|
||||
|
@ -375,6 +381,8 @@ class Table extends Component {
|
|||
|
||||
async deselectAll(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.notifySelection(this.props.onSelectionChangedAsync, new Map());
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ class TreeTable extends Component {
|
|||
treeData: nextProps.data
|
||||
});
|
||||
} else if (nextProps.dataUrl && this.props.dataUrl !== nextProps.dataUrl) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadData(next.props.dataUrl);
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +108,7 @@ class TreeTable extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
if (!this.props.data && this.props.dataUrl) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadData(this.props.dataUrl);
|
||||
}
|
||||
|
||||
|
@ -229,6 +231,7 @@ class TreeTable extends Component {
|
|||
const selection = this.destringifyKey(this.tree.getActiveNode().key);
|
||||
|
||||
if (selection !== this.props.selection) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onSelectionChanged(selection);
|
||||
}
|
||||
}
|
||||
|
@ -252,6 +255,7 @@ class TreeTable extends Component {
|
|||
}
|
||||
|
||||
if (updated) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onSelectionChanged(selection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,7 @@ export class UntrustedContentHost extends Component {
|
|||
|
||||
scheduleRefreshAccessToken() {
|
||||
this.refreshAccessTokenTimeout = setTimeout(() => {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.refreshAccessToken();
|
||||
this.scheduleRefreshAccessToken();
|
||||
}, 60 * 1000);
|
||||
|
@ -136,6 +137,7 @@ export class UntrustedContentHost extends Component {
|
|||
}
|
||||
|
||||
if (!this.state.hasAccessToken) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.refreshAccessToken();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
withErrorHandling
|
||||
} from '../../lib/error-handling';
|
||||
import {DeleteModalDialog} from "../../lib/modals";
|
||||
import {getImportTypes} from './helpers';
|
||||
import {getImportLabels} from './helpers';
|
||||
import {
|
||||
ImportSource,
|
||||
inProgress,
|
||||
|
@ -62,7 +62,7 @@ export default class CUD extends Component {
|
|||
|
||||
this.state = {};
|
||||
|
||||
const {importSourceLabels, mappingTypeLabels} = getImportTypes(props.t);
|
||||
const {importSourceLabels, mappingTypeLabels} = getImportLabels(props.t);
|
||||
|
||||
this.importSourceLabels = importSourceLabels;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '../../lib/page';
|
||||
import {withErrorHandling} from '../../lib/error-handling';
|
||||
import {Table} from '../../lib/table';
|
||||
import {getImportTypes} from './helpers';
|
||||
import {getImportLabels} from './helpers';
|
||||
import {Icon} from "../../lib/bootstrap-components";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import moment from "moment";
|
||||
|
@ -28,7 +28,7 @@ export default class List extends Component {
|
|||
|
||||
this.state = {};
|
||||
|
||||
const {importSourceLabels, importStatusLabels} = getImportTypes(props.t);
|
||||
const {importSourceLabels, importStatusLabels} = getImportLabels(props.t);
|
||||
this.importSourceLabels = importSourceLabels;
|
||||
this.importStatusLabels = importStatusLabels;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
withAsyncErrorHandler,
|
||||
withErrorHandling
|
||||
} from '../../lib/error-handling';
|
||||
import {getImportTypes} from './helpers';
|
||||
import {getImportLabels} from './helpers';
|
||||
import axios from "../../lib/axios";
|
||||
import {getUrl} from "../../lib/urls";
|
||||
import moment from "moment";
|
||||
|
@ -32,7 +32,7 @@ export default class Status extends Component {
|
|||
entity: props.entity
|
||||
};
|
||||
|
||||
const {importSourceLabels, importStatusLabels, runStatusLabels} = getImportTypes(props.t);
|
||||
const {importSourceLabels, importStatusLabels, runStatusLabels} = getImportLabels(props.t);
|
||||
this.importSourceLabels = importSourceLabels;
|
||||
this.importStatusLabels = importStatusLabels;
|
||||
this.runStatusLabels = runStatusLabels;
|
||||
|
@ -69,6 +69,7 @@ export default class Status extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.periodicRefreshTask();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
withAsyncErrorHandler,
|
||||
withErrorHandling
|
||||
} from '../../lib/error-handling';
|
||||
import {getImportTypes} from './helpers';
|
||||
import {getImportLabels} from './helpers';
|
||||
import {
|
||||
prepFinishedAndNotInProgress,
|
||||
runInProgress,
|
||||
|
@ -46,7 +46,7 @@ export default class Status extends Component {
|
|||
entity: props.entity
|
||||
};
|
||||
|
||||
const {importSourceLabels, importStatusLabels, runStatusLabels} = getImportTypes(props.t);
|
||||
const {importSourceLabels, importStatusLabels, runStatusLabels} = getImportLabels(props.t);
|
||||
this.importSourceLabels = importSourceLabels;
|
||||
this.importStatusLabels = importStatusLabels;
|
||||
this.runStatusLabels = runStatusLabels;
|
||||
|
@ -77,6 +77,7 @@ export default class Status extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.periodicRefreshTask();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import React from 'react';
|
||||
import {ImportSource, MappingType, ImportStatus, RunStatus} from '../../../../shared/imports';
|
||||
|
||||
export function getImportTypes(t) {
|
||||
export function getImportLabels(t) {
|
||||
|
||||
const importSourceLabels = {
|
||||
[ImportSource.CSV_FILE]: t('CSV file'),
|
||||
|
|
|
@ -85,6 +85,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
|
||||
if (!this.isEditGlobal()) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadTreeData();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ export default class CUD extends Component {
|
|||
state.formState = state.formState.setIn(['data', 'user_fields', 'value'], '');
|
||||
|
||||
if (newVal) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchUserFields(newVal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export default class Output extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadOutput();
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ export default class View extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.loadContent();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchPermissions();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue