Some bugfixes. The configuration management should be now OK.

This commit is contained in:
Tomas Bures 2018-04-22 20:29:35 +02:00
parent c12efeb97f
commit e97415c237
9 changed files with 209 additions and 142 deletions

View file

@ -3,18 +3,35 @@
import React, {Component} from "react"; import React, {Component} from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {translate} from "react-i18next"; import {translate} from "react-i18next";
import {NavButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from "../../lib/page"; import {
import {Button as FormButton, ButtonRow, Dropdown, Form, FormSendMethod, InputField, withForm} from "../../lib/form"; NavButton,
import {withAsyncErrorHandler, withErrorHandling} from "../../lib/error-handling"; requiresAuthenticatedUser,
Title,
Toolbar,
withPageHelpers
} from "../../lib/page";
import {
Button as FormButton,
ButtonRow,
Dropdown,
Form,
FormSendMethod,
InputField,
withForm
} from "../../lib/form";
import {withErrorHandling} from "../../lib/error-handling";
import {DeleteModalDialog} from "../../lib/modals"; import {DeleteModalDialog} from "../../lib/modals";
import interoperableErrors from "../../../../shared/interoperable-errors";
import styles from "./CUD.scss"; import styles from "./CUD.scss";
import {DragDropContext} from "react-dnd"; import {DragDropContext} from "react-dnd";
import HTML5Backend from "react-dnd-html5-backend"; import HTML5Backend from "react-dnd-html5-backend";
import TouchBackend from "react-dnd-touch-backend"; import TouchBackend from "react-dnd-touch-backend";
import SortableTree from "react-sortable-tree"; import SortableTree from "react-sortable-tree";
import {ActionLink, Button, Icon} from "../../lib/bootstrap-components"; import {
ActionLink,
Button,
Icon
} from "../../lib/bootstrap-components";
import {getRuleHelpers} from "./helpers"; import {getRuleHelpers} from "./helpers";
import RuleSettingsPane from "./RuleSettingsPane"; import RuleSettingsPane from "./RuleSettingsPane";
@ -88,18 +105,6 @@ export default class CUD extends Component {
return tree; return tree;
} }
@withAsyncErrorHandler
async loadFormValues() {
await this.getFormValuesFromURL(`/rest/segments/${this.props.list.id}/${this.props.entity.id}`, data => {
data.rootRuleType = data.settings.rootRule.type;
data.selectedRule = null; // Validation errors of the selected rule are attached to this which makes sure we don't submit the segment if the opened rule has errors
this.setState({
rulesTree: this.getTreeFromRules(data.settings.rootRule.rules)
});
});
}
componentDidMount() { componentDidMount() {
if (this.props.entity) { if (this.props.entity) {
this.setState({ this.setState({
@ -167,7 +172,14 @@ export default class CUD extends Component {
if (submitSuccessful) { if (submitSuccessful) {
if (stay) { if (stay) {
await this.loadFormValues(); await this.getFormValuesFromURL(`/rest/segments/${this.props.list.id}/${this.props.entity.id}`, data => {
data.rootRuleType = data.settings.rootRule.type;
data.selectedRule = null; // Validation errors of the selected rule are attached to this which makes sure we don't submit the segment if the opened rule has errors
this.setState({
rulesTree: this.getTreeFromRules(data.settings.rootRule.rules)
});
});
this.enableForm(); this.enableForm();
this.setFormStatusMessage('success', t('Segment saved')); this.setFormStatusMessage('success', t('Segment saved'));
} else { } else {

View file

@ -2,11 +2,32 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { translate, Trans } from 'react-i18next'; import {
import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page' Trans,
import { withForm, Form, FormSendMethod, InputField, TextArea, Dropdown, ACEEditor, ButtonRow, Button } from '../../lib/form'; translate
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling'; } from 'react-i18next';
import { validateNamespace, NamespaceSelect } from '../../lib/namespace'; import {
NavButton,
requiresAuthenticatedUser,
Title,
withPageHelpers
} from '../../lib/page'
import {
ACEEditor,
Button,
ButtonRow,
Dropdown,
Form,
FormSendMethod,
InputField,
TextArea,
withForm
} from '../../lib/form';
import {withErrorHandling} from '../../lib/error-handling';
import {
NamespaceSelect,
validateNamespace
} from '../../lib/namespace';
import {DeleteModalDialog} from "../../lib/modals"; import {DeleteModalDialog} from "../../lib/modals";
import mailtrainConfig from 'mailtrainConfig'; import mailtrainConfig from 'mailtrainConfig';
import 'brace/mode/javascript'; import 'brace/mode/javascript';
@ -33,11 +54,6 @@ export default class CUD extends Component {
entity: PropTypes.object entity: PropTypes.object
} }
@withAsyncErrorHandler
async loadFormValues() {
await this.getFormValuesFromURL(`/rest/report-templates/${this.props.entity.id}`);
}
componentDidMount() { componentDidMount() {
if (this.props.entity) { if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity); this.getFormValuesFromEntity(this.props.entity);
@ -259,7 +275,7 @@ export default class CUD extends Component {
if (submitSuccessful) { if (submitSuccessful) {
if (stay) { if (stay) {
await this.loadFormValues(); await this.getFormValuesFromURL(`/rest/report-templates/${this.props.entity.id}`);
this.enableForm(); this.enableForm();
this.setFormStatusMessage('success', t('Report template saved')); this.setFormStatusMessage('success', t('Report template saved'));
} else { } else {

View file

@ -99,13 +99,15 @@ export default class CUD extends Component {
localValidateFormValues(state) { localValidateFormValues(state) {
const t = this.props.t; const t = this.props.t;
const typeKey = state.getIn(['mailer_type', 'value']);
if (!state.getIn(['name', 'value'])) { if (!state.getIn(['name', 'value'])) {
state.setIn(['name', 'error'], t('Name must not be empty')); state.setIn(['name', 'error'], t('Name must not be empty'));
} else { } else {
state.setIn(['name', 'error'], null); state.setIn(['name', 'error'], null);
} }
if (!state.getIn(['mailer_type', 'value'])) { if (!typeKey) {
state.setIn(['mailer_type', 'error'], t('Mailer type must be selected')); state.setIn(['mailer_type', 'error'], t('Mailer type must be selected'));
} else { } else {
state.setIn(['mailer_type', 'error'], null); state.setIn(['mailer_type', 'error'], null);
@ -117,8 +119,11 @@ export default class CUD extends Component {
state.setIn(['verp_hostname', 'error'], null); state.setIn(['verp_hostname', 'error'], null);
} }
validateNamespace(t, state); validateNamespace(t, state);
if (typeKey) {
this.mailerTypes[typeKey].validate(state);
}
} }
async submitHandler() { async submitHandler() {
@ -157,7 +162,6 @@ export default class CUD extends Component {
const canDelete = isEdit && this.props.entity.permissions.includes('delete') && this.props.entity.id !== getSystemSendConfigurationId(); const canDelete = isEdit && this.props.entity.permissions.includes('delete') && this.props.entity.id !== getSystemSendConfigurationId();
const typeKey = this.getFormValue('mailer_type'); const typeKey = this.getFormValue('mailer_type');
console.log(typeKey);
let mailerForm = null; let mailerForm = null;
if (typeKey) { if (typeKey) {
mailerForm = this.mailerTypes[typeKey].getForm(this); mailerForm = this.mailerTypes[typeKey].getForm(this);
@ -210,12 +214,6 @@ export default class CUD extends Component {
: :
<Trans><p>VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it.</p></Trans> <Trans><p>VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it.</p></Trans>
} }
<InputField id="from_email" label={t('Default "from" email')}/>
<CheckBox id="from_email_overridable" text={t('Overridable')}/>
<InputField id="from_name" label={t('Default "from" name')}/>
<CheckBox id="from_name_overridable" text={t('Overridable')}/>
<InputField id="subject" label={t('Subject')}/>
<CheckBox id="subject_overridable" text={t('Overridable')}/>
</Fieldset> </Fieldset>
<ButtonRow> <ButtonRow>

View file

@ -18,6 +18,40 @@ export const mailerTypesOrder = [
MailerType.AWS_SES MailerType.AWS_SES
]; ];
export function getMailerTypes(t) {
const mailerTypes = {};
function initFieldsIfMissing(mutState, mailerType) {
const initVals = mailerTypes[mailerType].initData();
for (const key in initVals) {
if (!mutState.hasIn([key])) {
mutState.setIn([key, 'value'], initVals[key]);
}
}
}
function clearBeforeSave(data) {
for (const mailerKey in mailerTypes) {
const initVals = mailerTypes[mailerKey].initData();
for (const fieldKey in initVals) {
delete data[fieldKey];
}
}
}
function validateNumber(state, field, label, emptyAllowed = false) {
const value = state.getIn([field, 'value']);
if (typeof value === 'string' && value.trim() === '' && !emptyAllowed) { // After load, the numerical values can be still numbers
state.setIn([field, 'error'], t(`${label} must not be empty`));
} else if (isNaN(value)) {
state.setIn([field, 'error'], t(`${label} must be a number`));
} else {
state.setIn([field, 'error'], null);
}
}
function getInitCommon() { function getInitCommon() {
return { return {
maxConnections: '5', maxConnections: '5',
@ -43,7 +77,7 @@ function getInitGenericSMTP() {
function afterLoadCommon(data) { function afterLoadCommon(data) {
data.maxConnections = data.mailer_settings.maxConnections; data.maxConnections = data.mailer_settings.maxConnections;
data.throttling = data.mailer_settings.throttling || ''; data.throttling = data.mailer_settings.throttling || '';
data.logTransaction = data.mailer_settings.logTransactions; data.logTransactions = data.mailer_settings.logTransactions;
} }
function afterLoadGenericSMTP(data) { function afterLoadGenericSMTP(data) {
@ -62,7 +96,7 @@ function beforeSaveCommon(data) {
data.mailer_settings = {}; data.mailer_settings = {};
data.mailer_settings.maxConnections = Number(data.maxConnections); data.mailer_settings.maxConnections = Number(data.maxConnections);
data.mailer_settings.throttling = Number(data.throttling); data.mailer_settings.throttling = Number(data.throttling);
data.mailer_settings.logTransactions = data.logTransaction; data.mailer_settings.logTransactions = data.logTransactions;
} }
function beforeSaveGenericSMTP(data) { function beforeSaveGenericSMTP(data) {
@ -77,26 +111,15 @@ function beforeSaveGenericSMTP(data) {
data.mailer_settings.maxMessages = Number(data.smtpMaxMessages); data.mailer_settings.maxMessages = Number(data.smtpMaxMessages);
} }
export function getMailerTypes(t) { function validateCommon(state) {
const mailerTypes = {}; validateNumber(state, 'maxConnections', 'Max connections');
validateNumber(state, 'throttling', 'Throttling', true);
function initFieldsIfMissing(mutState, mailerType) {
const initVals = mailerTypes[mailerType].initData();
for (const key in initVals) {
if (!mutState.hasIn([key])) {
mutState.setIn([key, 'value'], initVals[key]);
}
}
} }
function clearBeforeSave(data) { function validateGenericSMTP(state) {
for (const mailerKey in mailerTypes) { validateCommon(state);
const initVals = mailerTypes[mailerKey].initData(); validateNumber(state, 'smtpPort', 'Port', true);
for (const fieldKey in initVals) { validateNumber(state, 'smtpMaxMessages', 'Max messages');
delete data[fieldKey];
}
}
} }
const typeOptions = [ const typeOptions = [
@ -153,6 +176,9 @@ export function getMailerTypes(t) {
}, },
afterTypeChange: mutState => { afterTypeChange: mutState => {
initFieldsIfMissing(mutState, MailerType.GENERIC_SMTP); initFieldsIfMissing(mutState, MailerType.GENERIC_SMTP);
},
validate: state => {
validateGenericSMTP(state);
} }
}; };
@ -212,6 +238,9 @@ export function getMailerTypes(t) {
}, },
afterTypeChange: mutState => { afterTypeChange: mutState => {
initFieldsIfMissing(mutState, MailerType.ZONE_MTA); initFieldsIfMissing(mutState, MailerType.ZONE_MTA);
},
validate: state => {
validateGenericSMTP(state);
} }
}; };
@ -251,6 +280,9 @@ export function getMailerTypes(t) {
}, },
afterTypeChange: mutState => { afterTypeChange: mutState => {
initFieldsIfMissing(mutState, MailerType.AWS_SES); initFieldsIfMissing(mutState, MailerType.AWS_SES);
},
validate: state => {
validateCommon(state);
} }
}; };

View file

@ -2,28 +2,26 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { translate, Trans } from 'react-i18next';
import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../lib/page';
import { import {
withForm, Trans,
translate
} from 'react-i18next';
import {
requiresAuthenticatedUser,
Title,
withPageHelpers
} from '../lib/page';
import {
Button,
ButtonRow,
Fieldset,
Form, Form,
FormSendMethod, FormSendMethod,
InputField, InputField,
TextArea, TextArea,
TableSelect, withForm
ButtonRow,
Button,
Dropdown,
StaticField,
CheckBox,
Fieldset
} from '../lib/form'; } from '../lib/form';
import {withErrorHandling} from '../lib/error-handling'; import {withErrorHandling} from '../lib/error-handling';
import { DeleteModalDialog } from '../lib/modals';
import { validateNamespace, NamespaceSelect } from '../lib/namespace';
import { UnsubscriptionMode } from '../../../shared/lists';
import styles from "../lib/styles.scss";
import mailtrainConfig from 'mailtrainConfig';
@translate() @translate()
@withForm @withForm
@ -60,7 +58,9 @@ export default class Update extends Component {
const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.PUT, '/rest/settings'); const submitSuccessful = await this.validateAndSendFormValuesToURL(FormSendMethod.PUT, '/rest/settings');
if (submitSuccessful) { if (submitSuccessful) {
this.navigateToWithFlashMessage('/settings', 'success', t('Global settings saved')); await this.getFormValuesFromURL('/rest/settings');
this.enableForm();
this.setFormStatusMessage('success', t('Global settings saved'));
} else { } else {
this.enableForm(); this.enableForm();
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.')); this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));

View file

@ -52,11 +52,6 @@ export default class CUD extends Component {
entity: PropTypes.object entity: PropTypes.object
} }
@withAsyncErrorHandler
async loadFormValues() {
await this.getFormValuesFromURL(`/rest/templates/${this.props.entity.id}`);
}
componentDidMount() { componentDidMount() {
if (this.props.entity) { if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity); this.getFormValuesFromEntity(this.props.entity);

View file

@ -2,15 +2,35 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { translate, Trans } from 'react-i18next'; import {translate} from 'react-i18next';
import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page' import {
import { withForm, Form, FormSendMethod, InputField, TextArea, Dropdown, ButtonRow, Button } from '../../lib/form'; NavButton,
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling'; requiresAuthenticatedUser,
import { validateNamespace, NamespaceSelect } from '../../lib/namespace'; Title,
withPageHelpers
} from '../../lib/page'
import {
Button,
ButtonRow,
Dropdown,
Form,
FormSendMethod,
InputField,
TextArea,
withForm
} from '../../lib/form';
import {withErrorHandling} from '../../lib/error-handling';
import {
NamespaceSelect,
validateNamespace
} from '../../lib/namespace';
import {DeleteModalDialog} from "../../lib/modals"; import {DeleteModalDialog} from "../../lib/modals";
import {versafix} from "../../../../shared/mosaico-templates"; import {versafix} from "../../../../shared/mosaico-templates";
import { getTemplateTypes, getTemplateTypesOrder } from "./helpers"; import {
getTemplateTypes,
getTemplateTypesOrder
} from "./helpers";
@translate() @translate()
@withForm @withForm
@ -42,13 +62,6 @@ export default class CUD extends Component {
entity: PropTypes.object entity: PropTypes.object
} }
@withAsyncErrorHandler
async loadFormValues() {
await this.getFormValuesFromURL(`/rest/mosaico-templates/${this.props.entity.id}`, data => {
this.templateTypes[data.type].afterLoad(data);
});
}
componentDidMount() { componentDidMount() {
if (this.props.entity) { if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity, data => { this.getFormValuesFromEntity(this.props.entity, data => {
@ -126,7 +139,9 @@ export default class CUD extends Component {
if (submitSuccessful) { if (submitSuccessful) {
if (stay) { if (stay) {
await this.loadFormValues(); await this.getFormValuesFromURL(`/rest/mosaico-templates/${this.props.entity.id}`, data => {
this.templateTypes[data.type].afterLoad(data);
});
this.enableForm(); this.enableForm();
this.setFormStatusMessage('success', t('Mosaico template saved')); this.setFormStatusMessage('success', t('Mosaico template saved'));
} else { } else {

View file

@ -11,12 +11,12 @@ const {MailerType, getSystemSendConfigurationId} = require('../shared/send-confi
const allowedKeys = new Set(['name', 'description', 'from_email', 'from_email_overridable', 'from_name', 'from_name_overridable', 'subject', 'subject_overridable', 'verp_hostname', 'mailer_type', 'mailer_settings', 'namespace']); const allowedKeys = new Set(['name', 'description', 'from_email', 'from_email_overridable', 'from_name', 'from_name_overridable', 'subject', 'subject_overridable', 'verp_hostname', 'mailer_type', 'mailer_settings', 'namespace']);
const allowedMailerTypes = new Set(Object.values(MailerType));
function hash(entity) { function hash(entity) {
return hasher.hash(filterObject(entity, allowedKeys)); return hasher.hash(filterObject(entity, allowedKeys));
} }
async function listDTAjax(context, params) { async function listDTAjax(context, params) {
return await dtHelpers.ajaxListWithPermissions( return await dtHelpers.ajaxListWithPermissions(
context, context,
@ -47,7 +47,7 @@ async function getById(context, id, withPermissions = true) {
async function _validateAndPreprocess(tx, entity, isCreate) { async function _validateAndPreprocess(tx, entity, isCreate) {
await namespaceHelpers.validateEntity(tx, entity); await namespaceHelpers.validateEntity(tx, entity);
enforce(entity.mailer_type >= 0 && entity.mailer_type < MailerType.MAX, 'Unknown mailer type'); enforce(allowedMailerTypes.has(entity.mailer_type), 'Unknown mailer type');
entity.mailer_settings = JSON.stringify(entity.mailer_settings); entity.mailer_settings = JSON.stringify(entity.mailer_settings);
} }

View file

@ -3,8 +3,7 @@
const MailerType = { const MailerType = {
GENERIC_SMTP: 'generic_smtp', GENERIC_SMTP: 'generic_smtp',
ZONE_MTA: 'zone_mta', ZONE_MTA: 'zone_mta',
AWS_SES: 'aws_ses', AWS_SES: 'aws_ses'
MAX: 3
}; };
function getSystemSendConfigurationId() { function getSystemSendConfigurationId() {