- Fix for #890
- "Channels" feature - Shoutout config param rendered on the homepage - "Clone" feature for campaigns
This commit is contained in:
parent
00432e6cfe
commit
d170548cfa
25 changed files with 1009 additions and 525 deletions
|
@ -25,7 +25,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
<h2>{t('Mailtrain 2 beta')}</h2>
|
||||
<div>{t('Build') + ' 2020-05-28-0102'}</div>
|
||||
<div>{t('Build') + ' 2020-07-17-0000'}</div>
|
||||
<p>{this.props.configItems.shoutout}</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -33,7 +33,7 @@ import {getUrl} from "../lib/urls";
|
|||
import {campaignOverridables, CampaignSource, CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
import {getCampaignLabels, ListsSelectorHelper} from "./helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Trans} from "react-i18next";
|
||||
|
@ -51,6 +51,8 @@ export default class CUD extends Component {
|
|||
|
||||
const t = props.t;
|
||||
|
||||
this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists');
|
||||
|
||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
|
||||
this.tagLanguages = getTagLanguages(props.t);
|
||||
|
||||
|
@ -79,20 +81,9 @@ export default class CUD extends Component {
|
|||
[CampaignSource.URL]: t('url')
|
||||
};
|
||||
|
||||
let sourceLabelsOrder;
|
||||
|
||||
if (props.createFromChannel) {
|
||||
// If a campaign is created within a channel, we allow only for those source types that makes sense
|
||||
sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN, CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
];
|
||||
|
||||
} else {
|
||||
// Regular creation or createFromCampaign
|
||||
sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
}
|
||||
const sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
|
||||
this.sourceOptions = [];
|
||||
for (const key of sourceLabelsOrder) {
|
||||
|
@ -113,8 +104,6 @@ export default class CUD extends Component {
|
|||
sendConfiguration: null
|
||||
};
|
||||
|
||||
this.nextListEntryId = 0;
|
||||
|
||||
this.initForm({
|
||||
leaveConfirmation: !props.entity || props.entity.permissions.includes('edit'),
|
||||
onChange: {
|
||||
|
@ -128,17 +117,11 @@ export default class CUD extends Component {
|
|||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
createFromChannel: PropTypes.object,
|
||||
createFromCampaign: PropTypes.object,
|
||||
crateFromCampaign: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
||||
getNextListEntryId() {
|
||||
const id = this.nextListEntryId;
|
||||
this.nextListEntryId += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
|
@ -156,10 +139,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
if (key && (match = key.match(/^(lists_[0-9]+_)list$/))) {
|
||||
const prefix = match[1];
|
||||
mutStateData.setIn([prefix + 'segment', 'value'], null);
|
||||
}
|
||||
this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||
|
@ -216,19 +196,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of data.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.getFormValuesMutator(data);
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(data.send_configuration);
|
||||
|
@ -275,27 +243,10 @@ export default class CUD extends Component {
|
|||
delete data[overridable + '_overriden'];
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lstUid of data.lists) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
const useSegmentation = data[prefix + 'useSegmentation'];
|
||||
|
||||
lsts.push({
|
||||
list: data[prefix + 'list'],
|
||||
segment: useSegmentation ? data[prefix + 'segment'] : null
|
||||
});
|
||||
}
|
||||
data.lists = lsts;
|
||||
|
||||
for (const key in data) {
|
||||
if (key.startsWith('data_') || key.startsWith('lists_')) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
this.listsSelectorHelper.submitFormValuesMutator(data);
|
||||
|
||||
return filterData(data, [
|
||||
'name', 'description', 'segment', 'namespace', 'send_configuration',
|
||||
'name', 'description', 'channel', 'namespace', 'send_configuration',
|
||||
'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
|
||||
'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
|
||||
'type', 'source', 'parent', 'lists'
|
||||
|
@ -314,10 +265,33 @@ export default class CUD extends Component {
|
|||
|
||||
const data = {};
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
|
||||
|
||||
if (this.props.createFromChannel) {
|
||||
const channel = this.props.createFromChannel;
|
||||
|
||||
data.channel = channel.id;
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (channel[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
|
@ -328,19 +302,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of channel.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.populateFrom(data, channel.lists);
|
||||
|
||||
data.type = CampaignType.REGULAR;
|
||||
|
||||
|
@ -366,23 +328,27 @@ export default class CUD extends Component {
|
|||
|
||||
if (channel.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data_sourceTemplate = channel.sourceTemplate;
|
||||
}
|
||||
|
||||
if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
channel.data_sourceCampaign = channel.data.sourceCampaign;
|
||||
}
|
||||
} else if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data_sourceCampaign = channel.data.sourceCampaign;
|
||||
|
||||
if (channel.source === CampaignSource.CUSTOM) {
|
||||
} else if (channel.source === CampaignSource.CUSTOM) {
|
||||
data.data_sourceCustom_type = channel.data.sourceCustom.type;
|
||||
data.data_sourceCustom_tag_language = channel.data.sourceCustom.tag_language;
|
||||
data.data_sourceCustom_data = channel.data.sourceCustom.data;
|
||||
|
||||
this.templateTypes[channel.data.sourceCustom.type].afterLoad(data);
|
||||
|
||||
} else if (channel.source === CampaignSource.URL) {
|
||||
data.data_sourceUrl = channel.data.sourceUrl
|
||||
}
|
||||
|
||||
|
||||
} else if (this.props.createFromCampaign) {
|
||||
const sourceCampaign = this.props.createFromCampaign;
|
||||
|
||||
data.channel = sourceCampaign.channel;
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (sourceCampaign[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
|
@ -393,19 +359,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of sourceCampaign.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.populateFrom(data, sourceCampaign.lists);
|
||||
|
||||
data.type = sourceCampaign.type;
|
||||
|
||||
|
@ -425,28 +379,6 @@ export default class CUD extends Component {
|
|||
|
||||
data.unsubscribe_url = sourceCampaign.unsubscribe_url;
|
||||
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
|
||||
if (sourceCampaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceCampaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || sourceCampaign.source === CampaignSource.CUSTOM) {
|
||||
data.source = CampaignSource.CUSTOM_FROM_CAMPAIGN;
|
||||
data.data_sourceCampaign = sourceCampaign.id;
|
||||
|
@ -466,18 +398,14 @@ export default class CUD extends Component {
|
|||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
data.channel = null;
|
||||
|
||||
data.type = this.props.type;
|
||||
|
||||
data.name = '';
|
||||
data.description = '';
|
||||
|
||||
const lstUid = this.getNextListEntryId();
|
||||
const lstPrefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[lstPrefix + 'list'] = null;
|
||||
data[lstPrefix + 'segment'] = null;
|
||||
data[lstPrefix + 'useSegmentation'] = false;
|
||||
data.lists = [lstUid];
|
||||
this.listsSelectorHelper.populateFrom(data, [{list: null, segment: null}]);
|
||||
|
||||
data.send_configuration = null;
|
||||
data.namespace = getDefaultNamespace(this.props.permissions);
|
||||
|
@ -490,27 +418,6 @@ export default class CUD extends Component {
|
|||
data.unsubscribe_url = '';
|
||||
|
||||
data.source = CampaignSource.CUSTOM;
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
}
|
||||
|
||||
this.populateFormValues(data);
|
||||
|
@ -583,17 +490,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
for (const lstUid of state.getIn(['lists', 'value'])) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
if (!state.getIn([prefix + 'list', 'value'])) {
|
||||
state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
|
||||
}
|
||||
|
||||
if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
|
||||
state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
|
||||
}
|
||||
}
|
||||
this.listsSelectorHelper.localValidateFormValues(state)
|
||||
|
||||
validateNamespace(t, state);
|
||||
}
|
||||
|
@ -627,7 +524,12 @@ export default class CUD extends Component {
|
|||
if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
|
||||
} else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
const channelId = this.getFormValue('channel');
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
}
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/campaigns-settings/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
|
@ -642,7 +544,12 @@ export default class CUD extends Component {
|
|||
if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${submitResult}/status`, 'success', t('campaignCreated'));
|
||||
} else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
|
||||
const channelId = this.getFormValue('channel');
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignCreated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
|
||||
}
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${submitResult}/edit`, 'success', t('campaignCreated'));
|
||||
}
|
||||
|
@ -654,46 +561,6 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onAddListEntry(orderBeforeIdx) {
|
||||
this.updateForm(mutState => {
|
||||
const lsts = mutState.getIn(['lists', 'value']);
|
||||
let paramId = 0;
|
||||
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
mutState.setIn([prefix + 'list', 'value'], null);
|
||||
mutState.setIn([prefix + 'segment', 'value'], null);
|
||||
mutState.setIn([prefix + 'useSegmentation', 'value'], false);
|
||||
|
||||
mutState.setIn(['lists', 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveListEntry(lstUid) {
|
||||
this.updateForm(mutState => {
|
||||
const lsts = this.getFormValue('lists');
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
mutState.delete(prefix + 'list');
|
||||
mutState.delete(prefix + 'segment');
|
||||
mutState.delete(prefix + 'useSegmentation');
|
||||
|
||||
mutState.setIn(['lists', 'value'], lsts.filter(val => val !== lstUid));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(orderIdx) {
|
||||
const lsts = this.getFormValue('lists');
|
||||
this.updateFormValue('lists', [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(orderIdx) {
|
||||
const lsts = this.getFormValue('lists');
|
||||
this.updateFormValue('lists', [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
@ -710,90 +577,13 @@ export default class CUD extends Component {
|
|||
extraSettings = <InputField id="data_feedUrl" label={t('rssFeedUrl')}/>
|
||||
}
|
||||
|
||||
const listsColumns = [
|
||||
const channelsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('subscribers') },
|
||||
{ data: 4, title: t('description') },
|
||||
{ data: 5, title: t('namespace') }
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('namespace') }
|
||||
];
|
||||
|
||||
const segmentsColumns = [
|
||||
{ data: 1, title: t('name') }
|
||||
];
|
||||
|
||||
const lstsEditEntries = [];
|
||||
const lsts = this.getFormValue('lists') || [];
|
||||
let lstOrderIdx = 0;
|
||||
for (const lstUid of lsts) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
const lstOrderIdxClosure = lstOrderIdx;
|
||||
|
||||
const selectedList = this.getFormValue(prefix + 'list');
|
||||
|
||||
lstsEditEntries.push(
|
||||
<div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
|
||||
<div className={campaignsStyles.entryButtons}>
|
||||
{lsts.length > 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="trash-alt"
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveListEntry(lstUid)}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('insertNewEntryBeforeThisOne')}
|
||||
onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
|
||||
/>
|
||||
{lstOrderIdx > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('moveUp')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
{lstOrderIdx < lsts.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('moveDown')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={campaignsStyles.entryContent}>
|
||||
<TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
||||
<div>
|
||||
<CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
|
||||
{selectedList && this.getFormValue(prefix + 'useSegmentation') &&
|
||||
<TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
lstOrderIdx += 1;
|
||||
}
|
||||
|
||||
const lstsEdit =
|
||||
<Fieldset label={t('lists')}>
|
||||
{lstsEditEntries}
|
||||
<div key="newEntry" className={campaignsStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('addList')}
|
||||
onClickAsync={() => this.onAddListEntry(lsts.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>;
|
||||
|
||||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
|
@ -938,13 +728,15 @@ export default class CUD extends Component {
|
|||
|
||||
<TextArea id="description" label={t('description')}/>
|
||||
|
||||
<TableSelect id="channel" label={t('Channel')} withHeader withClear dropdown dataUrl='rest/channels-with-create-campaign-permission-table' columns={channelsColumns} selectionLabelIndex={1} />
|
||||
|
||||
{extraSettings}
|
||||
|
||||
<NamespaceSelect/>
|
||||
|
||||
<hr/>
|
||||
|
||||
{lstsEdit}
|
||||
{this.listsSelectorHelper.render()}
|
||||
|
||||
<hr/>
|
||||
|
||||
|
|
|
@ -171,7 +171,12 @@ export default class CustomContent extends Component {
|
|||
if (afterSubmitAction === CustomContent.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
|
||||
} else if (afterSubmitAction === CustomContent.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
const channelId = this.props.entity.channel;
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
}
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/campaigns-content/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
|
|
|
@ -44,7 +44,7 @@ export default class List extends Component {
|
|||
|
||||
const channel = this.props.channel;
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createCampaign;
|
||||
const createPermitted = permissions.createCampaign && (!channel || channel.permissions.includes('createCampaign'));
|
||||
|
||||
const columns = [];
|
||||
columns.push({
|
||||
|
@ -184,7 +184,11 @@ export default class List extends Component {
|
|||
|
||||
<Title>{t('campaigns')}</Title>
|
||||
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
|
||||
{channel ?
|
||||
<Table ref={node => this.table = node} withHeader dataUrl={`rest/campaigns-by-channel-table/${channel.id}`} columns={columns} order={[5, 'desc']} />
|
||||
:
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||
import campaignsStyles from "./styles.scss";
|
||||
import {Button} from "../lib/bootstrap-components";
|
||||
import {CheckBox, Fieldset, TableSelect} from "../lib/form";
|
||||
import React from "react";
|
||||
|
||||
export function getCampaignLabels(t) {
|
||||
|
||||
|
@ -30,3 +34,251 @@ export function getCampaignLabels(t) {
|
|||
}
|
||||
|
||||
|
||||
export class ListsSelectorHelper {
|
||||
constructor(owner, t, id, allowEmpty = false) {
|
||||
this.owner = owner;
|
||||
this.t = t;
|
||||
this.id = id;
|
||||
this.nextEntryId = 0;
|
||||
this.allowEmpty = allowEmpty;
|
||||
|
||||
this.keyRegex = new RegExp(`^(${id}_[0-9]+_)list$`);
|
||||
}
|
||||
|
||||
getNextEntryId() {
|
||||
const id = this.nextEntryId;
|
||||
this.nextEntryId += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
getPrefix(lstUid) {
|
||||
return this.id + '_' + lstUid + '_';
|
||||
}
|
||||
|
||||
onAddListEntry(orderBeforeIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
owner.updateForm(mutState => {
|
||||
const lsts = mutState.getIn([id, 'value']);
|
||||
let paramId = 0;
|
||||
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
mutState.setIn([prefix + 'list', 'value'], null);
|
||||
mutState.setIn([prefix + 'segment', 'value'], null);
|
||||
mutState.setIn([prefix + 'useSegmentation', 'value'], false);
|
||||
|
||||
mutState.setIn([id, 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveListEntry(lstUid) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
owner.updateForm(mutState => {
|
||||
const lsts = owner.getFormValue(id);
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
mutState.delete(prefix + 'list');
|
||||
mutState.delete(prefix + 'segment');
|
||||
mutState.delete(prefix + 'useSegmentation');
|
||||
|
||||
mutState.setIn([id, 'value'], lsts.filter(val => val !== lstUid));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(orderIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const lsts = owner.getFormValue(id);
|
||||
owner.updateFormValue(id, [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(orderIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const lsts = owner.getFormValue(id);
|
||||
owner.updateFormValue(id, [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
|
||||
}
|
||||
|
||||
|
||||
// Public methods
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
if (key && (match = key.match(this.keyRegex))) {
|
||||
const prefix = match[1];
|
||||
mutStateData.setIn([prefix + 'segment', 'value'], null);
|
||||
}
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of data[id]) {
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data[id] = lsts;
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lstUid of data[id]) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
const useSegmentation = data[prefix + 'useSegmentation'];
|
||||
|
||||
lsts.push({
|
||||
list: data[prefix + 'list'],
|
||||
segment: useSegmentation ? data[prefix + 'segment'] : null
|
||||
});
|
||||
}
|
||||
data[id] = lsts;
|
||||
|
||||
for (const key in data) {
|
||||
if (key.startsWith('data_') || key.startsWith(id + '_')) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateFrom(data, lists) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of lists) {
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data[id] = lsts;
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const id = this.id;
|
||||
const t = this.t;
|
||||
|
||||
for (const lstUid of state.getIn([id, 'value'])) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
if (!state.getIn([prefix + 'list', 'value'])) {
|
||||
state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
|
||||
}
|
||||
|
||||
if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
|
||||
state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.t;
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const listsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('subscribers') },
|
||||
{ data: 4, title: t('description') },
|
||||
{ data: 5, title: t('namespace') }
|
||||
];
|
||||
|
||||
const segmentsColumns = [
|
||||
{ data: 1, title: t('name') }
|
||||
];
|
||||
|
||||
const lstsEditEntries = [];
|
||||
const lsts = owner.getFormValue(id) || [];
|
||||
let lstOrderIdx = 0;
|
||||
for (const lstUid of lsts) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
const lstOrderIdxClosure = lstOrderIdx;
|
||||
|
||||
const selectedList = owner.getFormValue(prefix + 'list');
|
||||
|
||||
lstsEditEntries.push(
|
||||
<div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
|
||||
<div className={campaignsStyles.entryButtons}>
|
||||
{(this.allowEmpty || lsts.length > 1) &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="trash-alt"
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveListEntry(lstUid)}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('insertNewEntryBeforeThisOne')}
|
||||
onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
|
||||
/>
|
||||
{lstOrderIdx > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('moveUp')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
{lstOrderIdx < lsts.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('moveDown')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={campaignsStyles.entryContent}>
|
||||
<TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
||||
<div>
|
||||
<CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
|
||||
{selectedList && owner.getFormValue(prefix + 'useSegmentation') &&
|
||||
<TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
lstOrderIdx += 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fieldset label={t('lists')}>
|
||||
{lstsEditEntries}
|
||||
<div key="newEntry" className={campaignsStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('addList')}
|
||||
onClickAsync={() => this.onAddListEntry(lsts.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
585
client/src/channels/CUD.js
Normal file
585
client/src/channels/CUD.js
Normal file
|
@ -0,0 +1,585 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withTranslation} from '../lib/i18n';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page'
|
||||
import {
|
||||
AlignedRow,
|
||||
Button,
|
||||
ButtonRow,
|
||||
CheckBox,
|
||||
Dropdown,
|
||||
Fieldset,
|
||||
filterData,
|
||||
Form,
|
||||
FormSendMethod,
|
||||
InputField,
|
||||
StaticField,
|
||||
TableSelect,
|
||||
TextArea,
|
||||
withForm,
|
||||
withFormErrorHandlers
|
||||
} from '../lib/form';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers';
|
||||
import axios from '../lib/axios';
|
||||
import styles from "../lib/styles.scss";
|
||||
import campaignsStyles from "./styles.scss";
|
||||
import {getUrl} from "../lib/urls";
|
||||
import {campaignOverridables, CampaignSource, CampaignStatus} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {getCampaignLabels, ListsSelectorHelper} from "../campaigns/helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Trans} from "react-i18next";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withForm,
|
||||
withErrorHandling,
|
||||
withPageHelpers,
|
||||
requiresAuthenticatedUser
|
||||
])
|
||||
export default class CUD extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists', true);
|
||||
|
||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN, true);
|
||||
this.tagLanguages = getTagLanguages(props.t);
|
||||
|
||||
this.mailerTypes = getMailerTypes(props.t);
|
||||
|
||||
const { campaignTypeLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
|
||||
this.sourceLabels = {
|
||||
[CampaignSource.CUSTOM]: t('customContent'),
|
||||
[CampaignSource.CUSTOM_FROM_CAMPAIGN]: t('customContentClonedFromAnotherCampaign'),
|
||||
[CampaignSource.TEMPLATE]: t('template'),
|
||||
[CampaignSource.CUSTOM_FROM_TEMPLATE]: t('customContentClonedFromTemplate'),
|
||||
[CampaignSource.URL]: t('url')
|
||||
};
|
||||
|
||||
const sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
|
||||
this.sourceOptions = [];
|
||||
for (const key of sourceLabelsOrder) {
|
||||
this.sourceOptions.push({key, label: this.sourceLabels[key]});
|
||||
}
|
||||
|
||||
this.customTemplateTypeOptions = [];
|
||||
for (const key of mailtrainConfig.editors) {
|
||||
this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName});
|
||||
}
|
||||
|
||||
this.customTemplateTagLanguageOptions = [];
|
||||
for (const key of mailtrainConfig.tagLanguages) {
|
||||
this.customTemplateTagLanguageOptions.push({key, label: this.tagLanguages[key].name});
|
||||
}
|
||||
|
||||
this.state = {
|
||||
sendConfiguration: null
|
||||
};
|
||||
|
||||
this.initForm({
|
||||
onChange: {
|
||||
send_configuration: ::this.onSendConfigurationChanged
|
||||
},
|
||||
onChangeBeforeValidation: ::this.onFormChangeBeforeValidation
|
||||
});
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
if (key === 'data_sourceCustom_type') {
|
||||
if (newValue) {
|
||||
this.templateTypes[newValue].afterTypeChange(mutStateData);
|
||||
}
|
||||
}
|
||||
|
||||
if (key === 'data_sourceCustom_tag_language') {
|
||||
if (newValue) {
|
||||
const currentType = this.getFormValue('data_sourceCustom_type');
|
||||
const isEdit = !!this.props.entity;
|
||||
this.templateTypes[currentType].afterTagLanguageChange(mutStateData, isEdit);
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||
newState.sendConfiguration = null;
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(sendConfigurationId);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchSendConfiguration(sendConfigurationId) {
|
||||
if (sendConfigurationId) {
|
||||
this.fetchSendConfigurationId = sendConfigurationId;
|
||||
|
||||
try {
|
||||
const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
|
||||
|
||||
if (sendConfigurationId === this.fetchSendConfigurationId) {
|
||||
this.setState({
|
||||
sendConfiguration: result.data
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof interoperableErrors.PermissionDeniedError) {
|
||||
this.setState({
|
||||
sendConfiguration: null
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateTemplateDefaults(data) {
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
this.populateTemplateDefaults(data);
|
||||
|
||||
if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data_sourceTemplate = data.data.sourceTemplate;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data_sourceCampaign = data.data.sourceCampaign;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM) {
|
||||
data.data_sourceCustom_type = data.data.sourceCustom.type;
|
||||
data.data_sourceCustom_tag_language = data.data.sourceCustom.tag_language;
|
||||
data.data_sourceCustom_data = data.data.sourceCustom.data;
|
||||
|
||||
this.templateTypes[data.data.sourceCustom.type].afterLoad(data);
|
||||
|
||||
} else if (data.source === CampaignSource.URL) {
|
||||
data.data_sourceUrl = data.data.sourceUrl
|
||||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (data[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
} else {
|
||||
data[overridable + '_overriden'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.getFormValuesMutator(data);
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(data.send_configuration);
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
const isEdit = !!this.props.entity;
|
||||
|
||||
data.source = Number.parseInt(data.source);
|
||||
|
||||
data.data = {};
|
||||
if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data.sourceTemplate = data.data_sourceTemplate;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data.sourceCampaign = data.data_sourceCampaign;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM) {
|
||||
this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
|
||||
|
||||
data.data.sourceCustom = {
|
||||
type: data.data_sourceCustom_type,
|
||||
tag_language: data.data_sourceCustom_tag_language,
|
||||
data: data.data_sourceCustom_data,
|
||||
}
|
||||
|
||||
} else if (data.source === CampaignSource.URL) {
|
||||
data.data.sourceUrl = data.data_sourceUrl;
|
||||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (!data[overridable + '_overriden']) {
|
||||
data[overridable + '_override'] = null;
|
||||
}
|
||||
delete data[overridable + '_overriden'];
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.submitFormValuesMutator(data);
|
||||
|
||||
return filterData(data, [
|
||||
'name', 'description', 'namespace', 'cpg_name', 'cpg_description', 'send_configuration',
|
||||
'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
|
||||
'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
|
||||
'source', 'lists'
|
||||
]);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.entity) {
|
||||
this.getFormValuesFromEntity(this.props.entity);
|
||||
|
||||
} else {
|
||||
|
||||
const data = {};
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
data.type = this.props.type;
|
||||
|
||||
data.name = '';
|
||||
data.description = '';
|
||||
|
||||
data.cpg_name = '';
|
||||
data.cpg_description = '';
|
||||
|
||||
this.listsSelectorHelper.populateFrom(data, []);
|
||||
|
||||
data.send_configuration = null;
|
||||
data.namespace = getDefaultNamespace(this.props.permissions);
|
||||
|
||||
data.subject = '';
|
||||
|
||||
data.click_tracking_disabled = false;
|
||||
data.open_tracking_disabled = false;
|
||||
|
||||
data.unsubscribe_url = '';
|
||||
|
||||
data.source = CampaignSource.CUSTOM;
|
||||
|
||||
this.populateTemplateDefaults(data);
|
||||
|
||||
this.populateFormValues(data);
|
||||
}
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
|
||||
for (const key of state.keys()) {
|
||||
state.setIn([key, 'error'], null);
|
||||
}
|
||||
|
||||
if (!state.getIn(['name', 'value'])) {
|
||||
state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
|
||||
}
|
||||
|
||||
const sourceTypeKey = Number.parseInt(state.getIn(['source', 'value']));
|
||||
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
if (!state.getIn(['data_sourceTemplate', 'value'])) {
|
||||
state.setIn(['data_sourceTemplate', 'error'], t('templateMustBeSelected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
if (!state.getIn(['data_sourceCampaign', 'value'])) {
|
||||
state.setIn(['data_sourceCampaign', 'error'], t('campaignMustBeSelected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']);
|
||||
if (!customTemplateTypeKey) {
|
||||
state.setIn(['data_sourceCustom_type', 'error'], t('typeMustBeSelected'));
|
||||
}
|
||||
|
||||
if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) {
|
||||
state.setIn(['data_sourceCustom_tag_language', 'error'], t('tagLanguageMustBeSelected'));
|
||||
}
|
||||
|
||||
if (customTemplateTypeKey) {
|
||||
this.templateTypes[customTemplateTypeKey].validate(state);
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.URL) {
|
||||
if (!state.getIn(['data_sourceUrl', 'value'])) {
|
||||
state.setIn(['data_sourceUrl', 'error'], t('urlMustNotBeEmpty'));
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.localValidateFormValues(state)
|
||||
|
||||
validateNamespace(t, state);
|
||||
}
|
||||
|
||||
async save() {
|
||||
await this.submitHandler();
|
||||
}
|
||||
|
||||
@withFormErrorHandlers
|
||||
async submitHandler(submitAndLeave) {
|
||||
const t = this.props.t;
|
||||
|
||||
let sendMethod, url;
|
||||
if (this.props.entity) {
|
||||
sendMethod = FormSendMethod.PUT;
|
||||
url = `rest/channels/${this.props.entity.id}`;
|
||||
} else {
|
||||
sendMethod = FormSendMethod.POST;
|
||||
url = 'rest/channels'
|
||||
}
|
||||
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('info', t('saving'));
|
||||
|
||||
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
|
||||
|
||||
if (submitResult) {
|
||||
if (this.props.entity) {
|
||||
if (submitAndLeave) {
|
||||
this.navigateToWithFlashMessage('/channels', 'success', t('Channel updated'));
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/channels/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('success', t('Channel updated'));
|
||||
}
|
||||
} else {
|
||||
if (submitAndLeave) {
|
||||
this.navigateToWithFlashMessage('/channels', 'success', t('Channel created'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/channels/${submitResult}/edit`, 'success', t('Channel created'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
const canModify = !isEdit || this.props.entity.permissions.includes('edit');
|
||||
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
|
||||
|
||||
const sourceTypeKey = Number.parseInt(this.getFormValue('source'));
|
||||
const campaignTypeKey = this.getFormValue('type');
|
||||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 6, title: t('namespace') }
|
||||
];
|
||||
|
||||
let sendSettings;
|
||||
if (this.getFormValue('send_configuration')) {
|
||||
if (this.state.sendConfiguration) {
|
||||
sendSettings = [];
|
||||
|
||||
const addOverridable = (id, label) => {
|
||||
if(this.state.sendConfiguration[id + '_overridable']){
|
||||
if (this.getFormValue(id + '_overriden')) {
|
||||
sendSettings.push(<InputField label={label} key={id + '_override'} id={id + '_override'}/>);
|
||||
} else {
|
||||
sendSettings.push(
|
||||
<StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
|
||||
{this.state.sendConfiguration[id]}
|
||||
</StaticField>
|
||||
);
|
||||
}
|
||||
sendSettings.push(<CheckBox key={id + '_overriden'} id={id + '_overriden'} text={t('override')} className={campaignsStyles.overrideCheckbox}/>);
|
||||
}
|
||||
else{
|
||||
sendSettings.push(
|
||||
<StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
|
||||
{this.state.sendConfiguration[id]}
|
||||
</StaticField>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
addOverridable('from_name', t('fromName'));
|
||||
addOverridable('from_email', t('fromEmailAddress'));
|
||||
addOverridable('reply_to', t('replytoEmailAddress'));
|
||||
} else {
|
||||
sendSettings = <AlignedRow>{t('loadingSendConfiguration')}</AlignedRow>
|
||||
}
|
||||
} else {
|
||||
sendSettings = null;
|
||||
}
|
||||
|
||||
let templateEdit = null;
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
||||
const templatesColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
{ data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
|
||||
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 6, title: t('namespace') },
|
||||
];
|
||||
|
||||
let help = null;
|
||||
if (sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
help = t('selectingATemplateCreatesACampaign');
|
||||
}
|
||||
|
||||
// The "key" property here and in the TableSelect below is to tell React that these tables are different and should be rendered by different instances. Otherwise, React will use
|
||||
// only one instance, which fails because Table does not handle updates in "columns" property
|
||||
templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
const campaignsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('type'), render: data => this.campaignTypeLabels[data] },
|
||||
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 6, title: t('namespace') }
|
||||
];
|
||||
|
||||
templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl='rest/campaigns-with-content-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('contentOfTheSelectedCampaignWillBeCopied')}/>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
||||
|
||||
let customTemplateTypeForm = null;
|
||||
|
||||
if (customTemplateTypeKey) {
|
||||
customTemplateTypeForm = getTypeForm(this, customTemplateTypeKey, false);
|
||||
}
|
||||
|
||||
templateEdit = <div>
|
||||
<Dropdown id="data_sourceCustom_type" label={t('type')} options={this.customTemplateTypeOptions}/>
|
||||
<Dropdown id="data_sourceCustom_tag_language" label={t('tagLanguage')} options={this.customTemplateTagLanguageOptions}/>
|
||||
|
||||
{customTemplateTypeForm}
|
||||
</div>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.URL) {
|
||||
templateEdit = <InputField id="data_sourceUrl" label={t('renderUrl')} help={t('ifAMessageIsSentThenThisUrlWillBePosTed')}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{canDelete &&
|
||||
<DeleteModalDialog
|
||||
stateOwner={this}
|
||||
visible={this.props.action === 'delete'}
|
||||
deleteUrl={`rest/channels/${this.props.entity.id}`}
|
||||
backUrl={`/channels/${this.props.entity.id}/edit`}
|
||||
successUrl="/channels"
|
||||
deletingMsg={t('Deleting channel ...')}
|
||||
deletedMsg={t('Channel deleted')}/>
|
||||
}
|
||||
|
||||
<Title>{isEdit ? t('Edit Channel') : t('Create Channel')}</Title>
|
||||
|
||||
{!canModify &&
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<Trans><b>Warning!</b> You do not have necessary permissions to edit this channel. Any changes that you perform here will be lost.</Trans>
|
||||
</div>
|
||||
}
|
||||
|
||||
{isEdit && this.props.entity.status === CampaignStatus.SENDING &&
|
||||
<div className={`alert alert-info`} role="alert">
|
||||
{t('formCannotBeEditedBecauseTheCampaignIs')}
|
||||
</div>
|
||||
}
|
||||
|
||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||
<InputField id="name" label={t('name')}/>
|
||||
|
||||
{isEdit &&
|
||||
<StaticField id="cid" className={styles.formDisabled} label={t('id')}>
|
||||
{this.getFormValue('cid')}
|
||||
</StaticField>
|
||||
}
|
||||
|
||||
<TextArea id="description" label={t('description')}/>
|
||||
|
||||
<NamespaceSelect/>
|
||||
|
||||
<hr/>
|
||||
<Fieldset label={t('Campaign defaults')}>
|
||||
<InputField id="cpg_name" label={t('Campaign name')}/>
|
||||
<TextArea id="cpg_description" label={t('Campaign description')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
|
||||
{this.listsSelectorHelper.render()}
|
||||
|
||||
<hr/>
|
||||
|
||||
<Fieldset label={t('sendSettings')}>
|
||||
|
||||
<TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader withClear dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
|
||||
|
||||
{sendSettings}
|
||||
|
||||
<InputField label={t('subjectLine')} key="subject" id="subject"/>
|
||||
|
||||
<InputField id="unsubscribe_url" label={t('customUnsubscribeUrl')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
|
||||
<Fieldset label={t('tracking')}>
|
||||
<CheckBox id="open_tracking_disabled" text={t('disableOpenedTracking')}/>
|
||||
<CheckBox id="click_tracking_disabled" text={t('disableClickedTracking')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
<Fieldset label={t('template')}>
|
||||
<Dropdown id="source" label={t('contentSource')} options={this.sourceOptions}/>
|
||||
</Fieldset>
|
||||
|
||||
{templateEdit}
|
||||
|
||||
<ButtonRow>
|
||||
{canModify &&
|
||||
<>
|
||||
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
|
||||
{isEdit && <Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')} onClickAsync={async () => await this.submitHandler(true)}/>}
|
||||
</>
|
||||
}
|
||||
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/channels/${this.props.entity.id}/delete`}/> }
|
||||
</ButtonRow>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ export default class List extends Component {
|
|||
data: 1,
|
||||
title: t('name'),
|
||||
actions: data => {
|
||||
const perms = data[10];
|
||||
const perms = data[5];
|
||||
if (perms.includes('view')) {
|
||||
return [{label: data[1], link: `/channels/${data[0]}/campaigns`}];
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import CampaignsList from '../campaigns/List';
|
||||
import CampaignsCUD from '../campaigns/CUD';
|
||||
import ChannelsList from './List';
|
||||
//import ChannelsCUD from './CUD';
|
||||
import ChannelsCUD from './CUD';
|
||||
import Share from '../shares/Share';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
@ -29,7 +29,7 @@ function getMenus(t) {
|
|||
panelRender: props => <ChannelsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':channelId([0-9]+)': {
|
||||
title: resolved => t('channelName', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
|
||||
title: resolved => t('Channel "{{name}}"', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
|
||||
resolve: {
|
||||
channel: params => `rest/channels/${params.channelId}`
|
||||
},
|
||||
|
@ -39,16 +39,14 @@ function getMenus(t) {
|
|||
title: t('Campaigns'),
|
||||
link: params => `/channels/${params.channelId}/campaigns`,
|
||||
visible: resolved => resolved.channel.permissions.includes('view'),
|
||||
panelRender: props => <CampaignsList channel={props.resolved.channel} />
|
||||
panelRender: props => <CampaignsList channel={props.resolved.channel} permissions={props.permissions} />
|
||||
},
|
||||
/*
|
||||
':action(edit|delete)': {
|
||||
title: t('edit'),
|
||||
link: params => `/channels/${params.channelId}/edit`,
|
||||
visible: resolved => resolved.channel.permissions.includes('view') || resolved.channel.permissions.includes('edit'),
|
||||
panelRender: props => <ChannelsCUD action={props.match.params.action} entity={props.resolved.channel} permissions={props.permissions} />
|
||||
},
|
||||
*/
|
||||
share: {
|
||||
title: t('share'),
|
||||
link: params => `/channels/${params.channelId}/share`,
|
||||
|
@ -61,7 +59,7 @@ function getMenus(t) {
|
|||
title: t('createCampaign'),
|
||||
link: params => `/channels/${params.channelId}/create`,
|
||||
visible: resolved => resolved.channel.permissions.includes('createCampaign'),
|
||||
panelRender: props => <CampaignsCUD action="create" channel={props.resolved.channel} permissions={props.permissions} />
|
||||
panelRender: props => <CampaignsCUD action="create" createFromChannel={props.resolved.channel} permissions={props.permissions} />,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -34,8 +34,6 @@ import styles from "./styles.scss";
|
|||
import moment from "moment";
|
||||
import {getUrl} from "./urls";
|
||||
import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
|
||||
import cudStyles from "../../../mvis/ivis-core/client/src/settings/jobs/CUD.scss";
|
||||
import {campaignOverridables, CampaignSource, CampaignType} from "../../../shared/campaigns";
|
||||
|
||||
|
||||
const FormState = {
|
||||
|
@ -931,207 +929,6 @@ class ButtonRow extends Component {
|
|||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withFormStateOwner
|
||||
], null, ['submitFormValuesMutator', 'getFormValueIdForPicker'])
|
||||
class ListCreator extends Component {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
className: PropTypes.string,
|
||||
withOrder: PropTypes.bool,
|
||||
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._nextEntryId = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const values = this.props.initValues;
|
||||
if (values && values.length > 0) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
let entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
if (!entryIds) {
|
||||
entryIds = [];
|
||||
}
|
||||
|
||||
for (const entryValue of values) {
|
||||
const entryId = this.getNextEntryId();
|
||||
mutState.setIn([this.getFormValueId(entryId), 'value'], entryValue);
|
||||
entryIds.push(entryId);
|
||||
}
|
||||
|
||||
mutState.setIn([this.props.id, 'value'], entryIds);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static getFormValuesMutator(pickerId, data) {
|
||||
const elems = [];
|
||||
for (const elem of data[pickerId]) {
|
||||
const uid = this.getNextEntryId();
|
||||
|
||||
const prefix = this.getFormValueId(uid);
|
||||
|
||||
data[prefix + 'list'] = elem.list;
|
||||
data[prefix + 'segment'] = elem.segment;
|
||||
data[prefix + 'useSegmentation'] = !!elem.segment;
|
||||
|
||||
elems.push(uid);
|
||||
}
|
||||
data[pickerId] = elems;
|
||||
}
|
||||
|
||||
|
||||
static submitFormValuesMutator(pickerId, data) {
|
||||
const entryValues = [];
|
||||
const entryIds = data[pickerId];
|
||||
|
||||
if (!entryIds) {
|
||||
return entryValues;
|
||||
}
|
||||
|
||||
for (const entryId of entryIds) {
|
||||
const entryFormId = ListCreator.getFormValueIdForPicker(pickerId, entryId);
|
||||
const value = data[entryFormId];
|
||||
entryValues.push(value);
|
||||
delete data[entryFormId]
|
||||
}
|
||||
|
||||
data[pickerId] = entryValues;
|
||||
}
|
||||
|
||||
static getFormValueIdForPicker(pickerId, entryId) {
|
||||
return `${pickerId}_${entryId}`;
|
||||
}
|
||||
|
||||
getFormValueId(entryId) {
|
||||
return ListCreator.getFormValueIdForPicker(this.props.id, entryId);
|
||||
}
|
||||
|
||||
getNextEntryId() {
|
||||
return this._nextEntryId++;
|
||||
}
|
||||
|
||||
onAddListEntry(positionBefore) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
let entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
if (!entryIds) {
|
||||
entryIds = [];
|
||||
}
|
||||
|
||||
if (positionBefore == null) {
|
||||
positionBefore = entryIds.length;
|
||||
}
|
||||
|
||||
const entryId = this.getNextEntryId();
|
||||
|
||||
mutState.setIn([this.getFormValueId(entryId), 'value'], null);
|
||||
mutState.setIn([this.props.id, 'value'], [...entryIds.slice(0, positionBefore), entryId, ...entryIds.slice(positionBefore)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveSetEntry(entryId) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
const entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
mutState.delete(this.getFormValueId(entryId));
|
||||
|
||||
mutState.setIn([this.props.id, 'value'], entryIds.filter(id => id !== entryId));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(position) {
|
||||
const owner = this.getFormStateOwner();
|
||||
const entryIds = owner.getFormValue(this.props.id);
|
||||
owner.updateFormValue(this.props.id, [...entryIds.slice(0, position - 1), entryIds[position], entryIds[position - 1], ...entryIds.slice(position + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(position) {
|
||||
const owner = this.getFormStateOwner();
|
||||
const entryIds = owner.getFormValue(this.props.id);
|
||||
owner.updateFormValue(this.props.id, [...entryIds.slice(0, position), entryIds[position + 1], entryIds[position], ...entryIds.slice(position + 2)]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const owner = this.getFormStateOwner();
|
||||
const id = props.id;
|
||||
const t = props.t;
|
||||
const withOrder = props.withOrder;
|
||||
const entries = [];
|
||||
const entryIds = owner.getFormValue(id) || [];
|
||||
|
||||
const entryButtonsStyles = withOrder ? cudStyles.entryButtonsWithOrder : cudStyles.entryButtons;
|
||||
for (let pos = 0; pos < entryIds.length; pos++) {
|
||||
const entryId = entryIds[pos];
|
||||
const elementId = this.getFormValueId(entryId);
|
||||
entries.push(
|
||||
<div key={entryId}
|
||||
className={cudStyles.entry + (withOrder ? ' ' + cudStyles.withOrder : '') + ' ' + cudStyles.entryWithButtons}>
|
||||
<div className={entryButtonsStyles}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon={`trash-alt ${withOrder ? "" : "fa-2x"}`}
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveSetEntry(entryId)}
|
||||
/>
|
||||
{withOrder &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('Insert new entry before this one')}
|
||||
onClickAsync={() => this.onAddListEntry(pos)}
|
||||
/>
|
||||
}
|
||||
{withOrder && pos > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('Move up')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(pos)}
|
||||
/>
|
||||
}
|
||||
{withOrder && pos < entryIds.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('Move down')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(pos)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={cudStyles.entryContent}>
|
||||
{React.cloneElement(this.props.entryElement, {id: elementId})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fieldset id={id} className={props.classname} help={props.help} flat={props.flat} label={props.label}>
|
||||
{entries}
|
||||
<div key="newEntry" className={cudStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('Add entry')}
|
||||
onClickAsync={() => this.onAddListEntry(entryIds.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withFormStateOwner
|
||||
])
|
||||
|
@ -1200,6 +997,7 @@ class TableSelect extends Component {
|
|||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
format: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
withClear: PropTypes.bool,
|
||||
|
||||
pageLength: PropTypes.number
|
||||
}
|
||||
|
@ -1245,6 +1043,15 @@ class TableSelect extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
async clear() {
|
||||
const owner = this.getFormStateOwner();
|
||||
if (this.props.selectMode === TableSelectMode.SINGLE && !this.props.selectionAsArray) {
|
||||
owner.updateFormValue(this.props.id, null);
|
||||
} else {
|
||||
owner.updateFormValue(this.props.id, []);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.table.refresh();
|
||||
}
|
||||
|
@ -1256,6 +1063,8 @@ class TableSelect extends Component {
|
|||
const htmlId = 'form_' + id;
|
||||
const t = props.t;
|
||||
|
||||
const selection = owner.getFormValue(id);
|
||||
|
||||
if (props.dropdown) {
|
||||
const className = owner.addFormValidationClass('form-control', id);
|
||||
|
||||
|
@ -1271,6 +1080,7 @@ class TableSelect extends Component {
|
|||
{!props.disabled &&
|
||||
<div className="input-group-append">
|
||||
<Button label={t('select')} className="btn-secondary" onClickAsync={::this.toggleOpen}/>
|
||||
{props.withClear && selection && <Button icon="times" title={t('Clear')} className="btn-secondary" onClickAsync={::this.clear}/>}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -1285,7 +1095,7 @@ class TableSelect extends Component {
|
|||
selectionAsArray={this.props.selectionAsArray}
|
||||
withHeader={props.withHeader}
|
||||
selectionKeyIndex={props.selectionKeyIndex}
|
||||
selection={owner.getFormValue(id)}
|
||||
selection={selection}
|
||||
onSelectionDataAsync={::this.onSelectionDataAsync}
|
||||
onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
|
@ -1305,7 +1115,7 @@ class TableSelect extends Component {
|
|||
selectionAsArray={this.props.selectionAsArray}
|
||||
withHeader={props.withHeader}
|
||||
selectionKeyIndex={props.selectionKeyIndex}
|
||||
selection={owner.getFormValue(id)}
|
||||
selection={selection}
|
||||
onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -211,7 +211,7 @@ function renderFrameWithContent(t, panelInFullScreen, showSidebar, primaryMenu,
|
|||
</div>
|
||||
|
||||
<footer key="appFooter" className="app-footer">
|
||||
<div className="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
|
||||
<div className="text-muted">© 2020 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,7 +39,7 @@ export function getTagLanguages(t) {
|
|||
};
|
||||
}
|
||||
|
||||
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE) {
|
||||
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE, allowEmpty = false) {
|
||||
// The prefix is used to to enable use within other forms (i.e. campaign form)
|
||||
const templateTypes = {};
|
||||
|
||||
|
@ -97,7 +97,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
dataUrl={`rest/mosaico-templates-by-tag-language-table/${tagLanguageKey}`}
|
||||
columns={mosaicoTemplatesColumns}
|
||||
selectionLabelIndex={1}
|
||||
disabled={isEdit}/>
|
||||
disabled={isEdit}
|
||||
withClear={allowEmpty}
|
||||
/>
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -169,7 +171,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
},
|
||||
validate: state => {
|
||||
const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
|
||||
if (!mosaicoTemplate) {
|
||||
if (!allowEmpty && !mosaicoTemplate) {
|
||||
state.setIn([prefix + 'mosaicoTemplate', 'error'], t('mosaicoTemplateMustBeSelected'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@ defaultRoles:
|
|||
sendConfiguration: [viewPublic, viewPrivate, edit, delete, share, sendWithoutOverrides, sendWithAllowedOverrides, sendWithAnyOverrides]
|
||||
list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
|
||||
customForm: [view, edit, delete, share]
|
||||
channel: [view, edit, delete, share]
|
||||
channel: [view, edit, delete, createCampaign, share]
|
||||
campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
|
||||
template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
|
||||
report: [view, edit, delete, share, execute, viewContent, viewOutput]
|
||||
|
@ -308,13 +308,13 @@ defaultRoles:
|
|||
|
||||
campaignsAdmin:
|
||||
name: Campaigns Admin
|
||||
description: In the respective namespace, the user has all permissions for managing lists, templates and campaigns and the permission to send to send configurations.
|
||||
description: In the respective namespace, the user has all permissions for managing lists, channels, templates and campaigns and the permission to send to send configurations.
|
||||
permissions: [view, edit, delete, share, createNamespace, createList, createCustomForm, createReport, createTemplate, createMosaicoTemplate, createChannel, createCampaign]
|
||||
children:
|
||||
sendConfiguration: [viewPublic, sendWithoutOverrides, sendWithAllowedOverrides]
|
||||
list: [view, edit, delete, share, viewFields, manageFields, viewSubscriptions, viewTestSubscriptions, manageSubscriptions, viewSegments, manageSegments, viewImports, manageImports, send, sendToTestUsers]
|
||||
customForm: [view, edit, delete, share]
|
||||
channel: [view, edit, delete, share]
|
||||
channel: [view, edit, delete, createCampaign, share]
|
||||
campaign: [view, edit, delete, share, viewFiles, manageFiles, viewAttachments, manageAttachments, viewTriggers, manageTriggers, send, sendToTestUsers, viewStats, fetchRss]
|
||||
template: [view, edit, delete, share, viewFiles, manageFiles, sendToTestUsers]
|
||||
report: [view, edit, delete, share, execute, viewContent, viewOutput]
|
||||
|
@ -325,7 +325,7 @@ defaultRoles:
|
|||
campaignsCreator:
|
||||
name: Campaigns Creator
|
||||
description: In the respective namespace, the user has all permissions to create and manage templates and campaigns. The user can also read public data about send configurations and use Mosaico templates in the namespace.
|
||||
permissions: [view, createTemplate, createChannel, createCampaign]
|
||||
permissions: [view, createTemplate, createCampaign]
|
||||
children:
|
||||
sendConfiguration: [viewPublic]
|
||||
channel: [view]
|
||||
|
|
|
@ -11,14 +11,14 @@ async function validateEntity(tx, entity) {
|
|||
}
|
||||
}
|
||||
|
||||
async function validateMove(context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
||||
async function validateMoveTx(tx, context, entity, existing, entityTypeId, createOperation, deleteOperation) {
|
||||
if (existing.namespace !== entity.namespace) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, createOperation);
|
||||
await shares.enforceEntityPermission(context, entityTypeId, entity.id, deleteOperation);
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, createOperation);
|
||||
await shares.enforceEntityPermissionTx(tx, context, entityTypeId, entity.id, deleteOperation);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateEntity,
|
||||
validateMove
|
||||
validateMoveTx
|
||||
};
|
|
@ -357,7 +357,7 @@ async function rawGetByTx(tx, key, id) {
|
|||
.leftJoin('campaign_lists', 'campaigns.id', 'campaign_lists.campaign')
|
||||
.groupBy('campaigns.id')
|
||||
.select([
|
||||
'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
|
||||
'campaigns.id', 'campaigns.cid', 'campaigns.name', 'campaigns.description', 'campaigns.channel', 'campaigns.namespace', 'campaigns.status', 'campaigns.type', 'campaigns.source',
|
||||
'campaigns.send_configuration', 'campaigns.from_name_override', 'campaigns.from_email_override', 'campaigns.reply_to_override', 'campaigns.subject',
|
||||
'campaigns.data', 'campaigns.click_tracking_disabled', 'campaigns.open_tracking_disabled', 'campaigns.unsubscribe_url', 'campaigns.scheduled',
|
||||
'campaigns.delivered', 'campaigns.unsubscribed', 'campaigns.bounced', 'campaigns.complained', 'campaigns.blacklisted', 'campaigns.opened', 'campaigns.clicks',
|
||||
|
@ -412,6 +412,7 @@ async function getByIdTx(tx, context, id, withPermissions = true, content = Cont
|
|||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
entity = {
|
||||
id: entity.id,
|
||||
channel: entity.channel,
|
||||
send_configuration: entity.send_configuration,
|
||||
|
||||
data: {
|
||||
|
@ -454,6 +455,8 @@ async function _validateAndPreprocess(tx, context, entity, isCreate, content) {
|
|||
|
||||
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
|
||||
}
|
||||
|
||||
enforce(Number.isInteger(entity.source));
|
||||
|
@ -481,6 +484,10 @@ async function _createTx(tx, context, entity, content) {
|
|||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCampaign');
|
||||
|
||||
if (entity.channel) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.channel, 'createCampaign');
|
||||
}
|
||||
|
||||
let copyFilesFrom = null;
|
||||
if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
copyFilesFrom = {
|
||||
|
@ -570,6 +577,13 @@ async function createRssTx(tx, context, entity) {
|
|||
return await _createTx(tx, context, entity, Content.RSS_ENTRY);
|
||||
}
|
||||
|
||||
async function _validateChannelMoveTx(tx, context, entity, existing) {
|
||||
if (existing.channel !== entity.channel) {
|
||||
await shares.enforceEntityPermission(context, 'channel', entity.channel, 'createCampaign');
|
||||
await shares.enforceEntityPermission(context, 'campaign', entity.id, 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity, content) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.id, 'edit');
|
||||
|
@ -585,11 +599,13 @@ async function updateWithConsistencyCheck(context, entity, content) {
|
|||
|
||||
let filteredEntity = filterObject(entity, allowedKeysUpdate);
|
||||
if (content === Content.ALL) {
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await _validateChannelMoveTx(tx, context, entity, existing);
|
||||
|
||||
} else if (content === Content.WITHOUT_SOURCE_CUSTOM) {
|
||||
filteredEntity.data.sourceCustom = existing.data.sourceCustom;
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'campaign', 'createCampaign', 'delete');
|
||||
await _validateChannelMoveTx(tx, context, entity, existing);
|
||||
|
||||
} else if (content === Content.ONLY_SOURCE_CUSTOM) {
|
||||
const data = existing.data;
|
||||
|
|
|
@ -16,8 +16,8 @@ const dependencyHelpers = require('../lib/dependency-helpers');
|
|||
const {EntityActivityType, CampaignActivityType} = require('../../shared/activity-log');
|
||||
const activityLog = require('../lib/activity-log');
|
||||
|
||||
const allowedKeys = ['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source'];
|
||||
const allowedKeys = new Set(['name', 'description', 'namespace', 'cpg_name', 'cpg_description',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url', 'source']);
|
||||
|
||||
|
||||
function hash(entity) {
|
||||
|
@ -43,12 +43,27 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function _getByTx(tx, key, id, withPermissions = true) {
|
||||
async function listWithCreateCampaignPermissionDTAjax(context, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'channel', requiredOperations: ['createCampaign'] }],
|
||||
params,
|
||||
builder => {
|
||||
builder = builder.from('channels')
|
||||
.innerJoin('namespaces', 'namespaces.id', 'channels.namespace');
|
||||
return builder;
|
||||
},
|
||||
['channels.id', 'channels.name', 'channels.cid', 'channels.description', 'namespaces.name']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function _getByTx(tx, context, key, id, withPermissions = true) {
|
||||
const entity = await tx('channels').where('channels.' + key, id)
|
||||
.leftJoin('channel_lists', 'channels.id', 'channel_lists.channel')
|
||||
.groupBy('channels.id')
|
||||
.select([
|
||||
'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.source',
|
||||
'channels.id', 'channels.name', 'channels.cid', 'channels.description', 'channels.namespace', 'channels.cpg_name', 'channels.cpg_description', 'channels.source',
|
||||
'channels.send_configuration', 'channels.from_name_override', 'channels.from_email_override', 'channels.reply_to_override', 'channels.subject',
|
||||
'channels.data', 'channels.click_tracking_disabled', 'channels.open_tracking_disabled', 'channels.unsubscribe_url',
|
||||
knex.raw(`GROUP_CONCAT(CONCAT_WS(\':\', channel_lists.list, channel_lists.segment) ORDER BY channel_lists.id SEPARATOR \';\') as lists`)
|
||||
|
@ -82,7 +97,7 @@ async function _getByTx(tx, key, id, withPermissions = true) {
|
|||
async function getByIdTx(tx, context, id, withPermissions = true) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', id, 'view');
|
||||
|
||||
return await _getByTx(tx, 'id', id, withPermissions);
|
||||
return await _getByTx(tx, context, 'id', id, withPermissions);
|
||||
}
|
||||
|
||||
async function getById(context, id, withPermissions = true) {
|
||||
|
@ -97,12 +112,13 @@ async function _validateAndPreprocess(tx, context, entity, isCreate) {
|
|||
if (entity.source !== null) {
|
||||
enforce(Number.isInteger(entity.source));
|
||||
|
||||
if (entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
if (entity.source === CampaignSource.TEMPLATE || entity.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'template', entity.data.sourceTemplate, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', entity.data.sourceCampaign, 'view');
|
||||
} else if (entity.source === CampaignSource.CUSTOM) {
|
||||
enforce(allTagLanguages.includes(entity.data.sourceCustom.tag_language), `Invalid tag language '${entity.data.sourceCustom.tag_language}'`);
|
||||
} else if (entity.source === CampaignSource.URL) {
|
||||
} else {
|
||||
enforce(false, 'Unknown channel source');
|
||||
}
|
||||
|
@ -156,7 +172,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'channel', entity.id, 'edit');
|
||||
|
||||
const existing = await _getByTx(tx, 'id', entity.id, false);
|
||||
const existing = await _getByTx(tx, context, 'id', entity.id, false);
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash !== entity.originalHash) {
|
||||
|
@ -166,7 +182,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await _validateAndPreprocess(tx, context, entity, false);
|
||||
|
||||
let filteredEntity = filterObject(entity, allowedKeys);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'channel', 'createCampaign', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'channel', 'createCampaign', 'delete');
|
||||
|
||||
await tx('channel_lists').where('channel', entity.id).del();
|
||||
await tx('channel_lists').insert(entity.lists.map(x => ({channel: entity.id, ...x})));
|
||||
|
@ -197,8 +213,8 @@ async function remove(context, id) {
|
|||
|
||||
|
||||
module.exports.hash = hash;
|
||||
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listWithCreateCampaignPermissionDTAjax = listWithCreateCampaignPermissionDTAjax;
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
module.exports.create = create;
|
||||
|
|
|
@ -169,7 +169,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'customForm', 'createCustomForm', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'customForm', 'createCustomForm', 'delete');
|
||||
|
||||
const form = filterObject(entity, allowedFormKeys);
|
||||
enforce(!Object.keys(checkForMjmlErrors(form)).length, 'Error(s) in form templates');
|
||||
|
|
|
@ -263,7 +263,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'list', 'createList', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'list', 'createList', 'delete');
|
||||
|
||||
await tx('lists').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'mosaicoTemplate', 'createMosaicoTemplate', 'delete');
|
||||
|
||||
await tx('mosaico_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
// namespaceHelpers.validateEntity is not needed here because it is part of the tree traversal check below
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'namespace', 'createNamespace', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'namespace', 'createNamespace', 'delete');
|
||||
|
||||
let iter = entity;
|
||||
while (iter.namespace != null) {
|
||||
|
|
|
@ -66,7 +66,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'reportTemplate', 'createReportTemplate', 'delete');
|
||||
|
||||
await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'report', 'createReport', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'report', 'createReport', 'delete');
|
||||
|
||||
entity.params = JSON.stringify(entity.params);
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
await _validateAndPreprocess(tx, entity);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'sendConfiguration', 'createSendConfiguration', 'delete');
|
||||
|
||||
await tx('send_configurations').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
await _validateAndPreprocess(tx, entity);
|
||||
entity.data = JSON.stringify(entity.data);
|
||||
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'template', 'createTemplate', 'delete');
|
||||
await namespaceHelpers.validateMoveTx(tx, context, entity, existing, 'template', 'createTemplate', 'delete');
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeys);
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ router.postAsync('/channels-table', passport.loggedIn, async (req, res) => {
|
|||
return res.json(await channels.listDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.postAsync('/channels-with-create-campaign-permission-table', passport.loggedIn, async (req, res) => {
|
||||
return res.json(await channels.listWithCreateCampaignPermissionDTAjax(req.context, req.body));
|
||||
});
|
||||
|
||||
router.getAsync('/channels/:channelId', passport.loggedIn, async (req, res) => {
|
||||
const channel = await channels.getById(req.context, castToInteger(req.params.channelId), true);
|
||||
channel.hash = channels.hash(channel);
|
||||
|
@ -25,7 +29,7 @@ router.putAsync('/channels/:channelId', passport.loggedIn, passport.csrfProtecti
|
|||
const entity = req.body;
|
||||
entity.id = castToInteger(req.params.channelId);
|
||||
|
||||
await channels.updateWithConsistencyCheck(req.context);
|
||||
await channels.updateWithConsistencyCheck(req.context, entity);
|
||||
return res.json();
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"zone-mta": "^2.2.1",
|
||||
"zone-mta": "^2.3.0",
|
||||
"zonemta-delivery-counters": "^1.0.1",
|
||||
"zonemta-limiter": "^1.0.0",
|
||||
"zonemta-loop-breaker": "^1.0.2"
|
||||
|
|
Loading…
Reference in a new issue