Field setup wizard for new list - addresses 1st line of #510

Bugfixes to address #511
This commit is contained in:
Tomas Bures 2018-12-31 09:45:59 +00:00
parent de55870561
commit b26f5008da
10 changed files with 144 additions and 59 deletions

View file

@ -107,8 +107,6 @@ export default class StatisticsOpened extends Component {
{ data: 5, title: t('opensCount') }
];
console.log(this.state.statisticsOpened);
const renderNavPill = (key, label) => (
<li role="presentation" className={agg === key ? 'active' : ''}>
<Link to={`/campaigns/${entity.id}/statistics/opened/${key}`}>{label}</Link>
@ -223,8 +221,6 @@ export default class StatisticsOpened extends Component {
);
}
console.log(mailtrainConfig);
return (
<div>
<Title>{t('detailedStatistics')}</Title>

View file

@ -1,3 +1,5 @@
@import "../../static/scss/variables";
.form { // This is here to give the styles below higher priority than Bootstrap has
:global .DayPicker {
border-left: 1px solid lightgray;
@ -148,7 +150,8 @@
}
.iconDisabled {
color: #bf3e11;
color: $link-color;
text-decoration: $link-decoration;
}
.dependenciesList {

View file

@ -30,7 +30,7 @@ import {
NamespaceSelect,
validateNamespace
} from '../lib/namespace';
import {UnsubscriptionMode} from '../../../shared/lists';
import {UnsubscriptionMode, FieldWizard} from '../../../shared/lists';
import styles
from "../lib/styles.scss";
import mailtrainConfig
@ -78,7 +78,8 @@ export default class CUD extends Component {
homepage: '',
unsubscription_mode: UnsubscriptionMode.ONE_STEP,
namespace: mailtrainConfig.user.namespace,
to_name: '[MERGE_FIRST_NAME] [MERGE_LAST_NAME]',
to_name: '',
fieldWizard: FieldWizard.FIRST_LAST_NAME,
send_configuration: null,
listunsubscribe_disabled: false
});
@ -129,6 +130,10 @@ export default class CUD extends Component {
data.default_form = null;
}
delete data.form;
if (data.fieldWizard === FieldWizard.FIRST_LAST_NAME || data.fieldWizard === FieldWizard.NAME) {
data.to_name = null;
}
});
if (submitSuccessful) {
@ -193,6 +198,32 @@ export default class CUD extends Component {
{ data: 6, title: t('namespace') }
];
let toNameFields;
if (isEdit) {
toNameFields = <InputField id="to_name" label={t('recipientsNameTemplate')} help={t('specifyUsingMergeTagsOfThisListHowTo')}/>;
} else {
const fieldWizardOptions = [
{key: FieldWizard.NONE, label: t('Empty / Custom (no fields)')},
{key: FieldWizard.NAME, label: t('Name (one field)')},
{key: FieldWizard.FIRST_LAST_NAME, label: t('First name and Last name (two fields)')},
];
const fieldWizardValue = this.getFormValue('fieldWizard');
const fieldWizardSelector = <Dropdown id="fieldWizard" label={t('Representation of subscriber\'s name')} options={fieldWizardOptions} help={t('Select how the name of a subscriber will be represented. The fields in list will be created accordingly. You can always adjust the choice later by editing the list fields. If you select "Empty / Custom", provide a template below in "Recipients name template" that will be used as subscriber\'s name as it will appear in the emails\' "To" field.')}/>
if (fieldWizardValue === FieldWizard.NONE) {
toNameFields = (
<>
{fieldWizardSelector}
<InputField id="to_name" label={t('recipientsNameTemplate')} help={t('specifyUsingMergeTagsOfThisListHowTo')}/>
</>
);
} else {
toNameFields = fieldWizardSelector;
}
}
return (
<div>
{canDelete &&
@ -221,7 +252,7 @@ export default class CUD extends Component {
<InputField id="contact_email" label={t('contactEmail')} help={t('contactEmailUsedInSubscriptionFormsAnd')}/>
<InputField id="homepage" label={t('homepage')} help={t('homepageUrlUsedInSubscriptionFormsAnd')}/>
<InputField id="to_name" label={t('recipientsNameTemplate')} help={t('specifyUsingMergeTagsOfThisListHowTo')}/>
{toNameFields}
<TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} help={t('sendConfigurationThatWillBeUsedFor')}/>
<NamespaceSelect/>

View file

@ -71,6 +71,10 @@ export default class CUD extends Component {
};
this.initForm();
this.onRuleSettingsPaneUpdatedHandler = ::this.onRuleSettingsPaneUpdated;
this.onRuleSettingsPaneCloseHandler = ::this.onRuleSettingsPaneClose;
this.onRuleSettingsPaneDeleteHandler = ::this.onRuleSettingsPaneDelete;
}
static propTypes = {
@ -334,7 +338,6 @@ export default class CUD extends Component {
}
}
return (
<div>
@ -411,7 +414,7 @@ export default class CUD extends Component {
<div className={styles.rightPane}>
<div className={styles.rightPaneInner}>
{selectedRule &&
<RuleSettingsPane rule={selectedRule} fields={this.props.fields} onChange={::this.onRuleSettingsPaneUpdated} onClose={::this.onRuleSettingsPaneClose} onDelete={::this.onRuleSettingsPaneDelete} forceShowValidation={this.isFormValidationShown()}/>}
<RuleSettingsPane rule={selectedRule} fields={this.props.fields} onChange={this.onRuleSettingsPaneUpdatedHandler} onClose={this.onRuleSettingsPaneCloseHandler} onDelete={this.onRuleSettingsPaneDeleteHandler} forceShowValidation={this.isFormValidationShown()}/>}
</div>
</div>
</div>

View file

@ -1,6 +1,6 @@
'use strict';
import React, {Component} from "react";
import React, {PureComponent} from "react";
import PropTypes
from "prop-types";
import {withTranslation} from '../../lib/i18n';
@ -31,7 +31,7 @@ import {withComponentMixins} from "../../lib/decorator-helpers";
withPageHelpers,
requiresAuthenticatedUser
])
export default class CUD extends Component {
export default class RuleSettingsPane extends PureComponent {
constructor(props) {
super(props);
@ -136,7 +136,7 @@ export default class CUD extends Component {
if (type) {
const settings = ruleHelpers.primitiveRuleTypes[colType][type];
if (!settings) {
// The existing rule type does not fit the newly changed column. This resets the rule type chooser to "-- Select ---"
// The existing rule type does not fit the newly changed column. This resets the rule type chooser to "--- Select ---"
mutState.setIn(['type', 'value'], '');
}
}
@ -212,7 +212,7 @@ export default class CUD extends Component {
const ruleType = this.getFormValue('type');
if (ruleType) {
ruleSettings = ruleHelpers.primitiveRuleTypes[colType][ruleType].form;
ruleSettings = ruleHelpers.primitiveRuleTypes[colType][ruleType].getForm();
}
}
}
@ -226,8 +226,6 @@ export default class CUD extends Component {
</div>;
}
return (
<div className={styles.ruleOptions}>
<h3>{t('ruleOptions')}</h3>

View file

@ -216,7 +216,7 @@ export function getRuleHelpers(t, fields) {
const stringValueSettings = allowEmpty => ({
form: <InputField id="value" label={t('value')} />,
getForm: () => <InputField id="value" label={t('value')} />,
getFormData: rule => ({
value: rule.value
}),
@ -233,7 +233,7 @@ export function getRuleHelpers(t, fields) {
});
const numberValueSettings = {
form: <InputField id="value" label={t('value')} />,
getForm: () => <InputField id="value" label={t('value')} />,
getFormData: rule => ({
value: rule.value.toString()
}),
@ -253,7 +253,7 @@ export function getRuleHelpers(t, fields) {
};
const birthdayValueSettings = {
form: <DatePicker id="birthday" label={t('date')} birthday />,
getForm: () => <DatePicker id="birthday" label={t('date')} birthday />,
getFormData: rule => ({
birthday: formatBirthday(DateFormat.INTL, rule.value)
}),
@ -274,7 +274,7 @@ export function getRuleHelpers(t, fields) {
};
const dateValueSettings = {
form: <DatePicker id="date" label={t('date')} />,
getForm: () => <DatePicker id="date" label={t('date')} />,
getFormData: rule => ({
date: formatDate(DateFormat.INTL, rule.value)
}),
@ -295,7 +295,7 @@ export function getRuleHelpers(t, fields) {
};
const dateRelativeValueSettings = {
form:
getForm: () =>
<div>
<InputField id="daysValue" label={t('numberOfDays')}/>
<Dropdown id="direction" label={t('beforeAfter')} options={[
@ -324,7 +324,7 @@ export function getRuleHelpers(t, fields) {
};
const optionValueSettings = {
form: null,
getForm: () => null,
getFormData: rule => ({}),
assignRuleSettings: (rule, getter) => {},
validate: state => {}

View file

@ -532,41 +532,45 @@ async function _sortIn(tx, listId, entityId, orderListBefore, orderSubscribeBefo
}
}
async function createTx(tx, context, listId, entity) {
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
await _validateAndPreprocess(tx, listId, entity, true);
const fieldType = fieldTypes[entity.type];
let columnName;
if (!fieldType.grouped) {
columnName = ('custom_' + slugify(entity.name, '_') + '_' + shortid.generate()).toLowerCase().replace(/[^a-z0-9_]/g, '');
}
const filteredEntity = filterObject(entity, allowedKeysCreate);
filteredEntity.list = listId;
filteredEntity.column = columnName;
const ids = await tx('custom_fields').insert(filteredEntity);
const id = ids[0];
await _sortIn(tx, listId, id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
if (columnName) {
await knex.schema.table('subscription__' + listId, table => {
fieldType.addColumn(table, columnName);
if (fieldType.indexed) {
table.index(columnName);
}
});
// Altough this is a reference to an import, it is represented as signed int(11). This is because we use negative values for constant from SubscriptionSource
await knex.schema.raw('ALTER TABLE `subscription__' + listId + '` ADD `source_' + columnName +'` int(11) DEFAULT NULL');
}
return id;
}
async function create(context, listId, entity) {
return await knex.transaction(async tx => {
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
await _validateAndPreprocess(tx, listId, entity, true);
const fieldType = fieldTypes[entity.type];
let columnName;
if (!fieldType.grouped) {
columnName = ('custom_' + slugify(entity.name, '_') + '_' + shortid.generate()).toLowerCase().replace(/[^a-z0-9_]/g, '');
}
const filteredEntity = filterObject(entity, allowedKeysCreate);
filteredEntity.list = listId;
filteredEntity.column = columnName;
const ids = await tx('custom_fields').insert(filteredEntity);
const id = ids[0];
await _sortIn(tx, listId, id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
if (columnName) {
await knex.schema.table('subscription__' + listId, table => {
fieldType.addColumn(table, columnName);
if (fieldType.indexed) {
table.index(columnName);
}
});
// Altough this is a reference to an import, it is represented as signed int(11). This is because we use negative values for constant from SubscriptionSource
await knex.schema.raw('ALTER TABLE `subscription__' + listId + '` ADD `source_' + columnName +'` int(11) DEFAULT NULL');
}
return id;
return await createTx(tx, context, listId, entity);
});
}
@ -833,6 +837,7 @@ module.exports.listByOrderListTx = listByOrderListTx;
module.exports.listDTAjax = listDTAjax;
module.exports.listGroupedDTAjax = listGroupedDTAjax;
module.exports.create = create;
module.exports.createTx = createTx;
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
module.exports.remove = remove;
module.exports.removeAllByListIdTx = removeAllByListIdTx;

View file

@ -14,7 +14,7 @@ const imports = require('./imports');
const entitySettings = require('../lib/entity-settings');
const dependencyHelpers = require('../lib/dependency-helpers');
const UnsubscriptionMode = require('../../shared/lists').UnsubscriptionMode;
const {UnsubscriptionMode, FieldWizard} = require('../../shared/lists');
const allowedKeys = new Set(['name', 'description', 'default_form', 'public_subscribe', 'unsubscription_mode', 'contact_email', 'homepage', 'namespace', 'to_name', 'listunsubscribe_disabled', 'send_configuration']);
@ -112,6 +112,47 @@ async function create(context, entity) {
await _validateAndPreprocess(tx, entity);
const fieldsToAdd = [];
const fieldWizard = entity.fieldWizard;
if (fieldWizard === FieldWizard.FIRST_LAST_NAME) {
if (entity.to_name === null) {
entity.to_name = '[MERGE_FIRST_NAME] [MERGE_LAST_NAME]';
}
fieldsToAdd.push({
name: 'First name',
key: 'MERGE_FIRST_NAME',
default_value: '',
type: 'text',
group: null,
settings: {}
});
fieldsToAdd.push({
name: 'Last name',
key: 'MERGE_LAST_NAME',
default_value: '',
type: 'text',
group: null,
settings: {}
});
} else if (fieldWizard === FieldWizard.NAME) {
if (entity.to_name === null) {
entity.to_name = '[MERGE_NAME]';
}
fieldsToAdd.push({
name: 'Name',
key: 'MERGE_NAME',
default_value: '',
type: 'text',
group: null,
settings: {}
});
}
const filteredEntity = filterObject(entity, allowedKeys);
filteredEntity.cid = shortid.generate();
@ -148,6 +189,10 @@ async function create(context, entity) {
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'list', entityId: id });
for (const fld of fieldsToAdd) {
await fields.createTx(tx, context, id, fld);
}
return id;
});
}

View file

@ -44,9 +44,6 @@ router.post('/login', passport.csrfProtection, passport.restLogin);
router.post('/logout', passport.csrfProtection, passport.restLogout);
router.postAsync('/password-reset-send', passport.csrfProtection, async (req, res) => {
// FIXME
console.log(req.locale);
console.log(req.cookies);
await users.sendPasswordReset(req.locale, req.body.usernameOrEmail);
return res.json();
});

View file

@ -32,6 +32,12 @@ const SubscriptionSource = {
ERASED: -6
};
const FieldWizard = {
NONE: 'none',
NAME: 'full_name',
FIRST_LAST_NAME: 'first_last_name'
}
function getFieldColumn(field) {
return field.column || 'grouped_' + field.id;
}
@ -40,5 +46,6 @@ module.exports = {
UnsubscriptionMode,
SubscriptionStatus,
SubscriptionSource,
FieldWizard,
getFieldColumn
};