Add warnings to manipulation step
[MAILPOET-1809]
This commit is contained in:
@@ -99,6 +99,10 @@ tr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mailpoet_subscribers_data_parse_results_details_show {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.mailpoet_import_validation_step {
|
.mailpoet_import_validation_step {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@@ -11,97 +11,19 @@ import StepInputValidation from './import/step_input_validation.jsx';
|
|||||||
import StepMethodSelection from './import/step_method_selection.jsx';
|
import StepMethodSelection from './import/step_method_selection.jsx';
|
||||||
import StepResults from './import/step_results.jsx';
|
import StepResults from './import/step_results.jsx';
|
||||||
|
|
||||||
const SUBSCRIBERS_LIMIT_FOR_VALIDATION = 500;
|
|
||||||
|
|
||||||
function getDataManipulationPreviousStepLink(importData) {
|
|
||||||
if (importData === undefined) {
|
|
||||||
return 'step_method_selection';
|
|
||||||
}
|
|
||||||
if (importData.subscribersCount === undefined) {
|
|
||||||
return 'step_method_selection';
|
|
||||||
}
|
|
||||||
if (importData.subscribersCount < SUBSCRIBERS_LIMIT_FOR_VALIDATION) {
|
|
||||||
return 'step_method_selection';
|
|
||||||
}
|
|
||||||
return 'step_input_validation';
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery(document).ready(() => {
|
jQuery(document).ready(() => {
|
||||||
if (!jQuery('#mailpoet_subscribers_import').length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
jQuery('input[name="select_method"]').attr('checked', false);
|
|
||||||
// configure router
|
|
||||||
const router = new (Backbone.Router.extend({
|
|
||||||
routes: {
|
|
||||||
'': 'home',
|
|
||||||
step_method_selection: 'step_method_selection',
|
|
||||||
step_input_validation: 'step_input_validation',
|
|
||||||
step_data_manipulation: 'step_data_manipulation',
|
|
||||||
step_results: 'step_results',
|
|
||||||
},
|
|
||||||
home() {
|
|
||||||
this.navigate('step_method_selection', { trigger: true });
|
|
||||||
},
|
|
||||||
}))();
|
|
||||||
|
|
||||||
function showCurrentStep() {
|
|
||||||
MailPoet.Notice.hide();
|
|
||||||
MailPoet.Modal.loading(false);
|
|
||||||
jQuery('#mailpoet_subscribers_import > div[id^="step"]').hide();
|
|
||||||
jQuery(window.location.hash).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
router.on('route:step_method_selection', () => {
|
|
||||||
showCurrentStep();
|
|
||||||
|
|
||||||
const container = document.getElementById('step_method_selection');
|
|
||||||
|
|
||||||
if (container) {
|
|
||||||
ReactDOM.render(
|
|
||||||
<StepMethodSelection
|
|
||||||
navigate={router.navigate}
|
|
||||||
/>,
|
|
||||||
container
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.on('route:step_input_validation', () => {
|
|
||||||
if (typeof (window.importData.step_method_selection) === 'undefined') {
|
|
||||||
router.navigate('step_method_selection', { trigger: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showCurrentStep();
|
|
||||||
const container = document.getElementById('step_input_validation');
|
|
||||||
|
|
||||||
if (container) {
|
|
||||||
ReactDOM.render(
|
|
||||||
<StepInputValidation
|
|
||||||
navigate={router.navigate}
|
|
||||||
importData={window.importData.step_method_selection}
|
|
||||||
/>,
|
|
||||||
container
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.on('route:step_data_manipulation', () => {
|
router.on('route:step_data_manipulation', () => {
|
||||||
let fillerPosition;
|
let fillerPosition;
|
||||||
let importResults;
|
|
||||||
let duplicates;
|
|
||||||
if (typeof (window.importData.step_method_selection) === 'undefined') {
|
|
||||||
router.navigate('step_method_selection', { trigger: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// define reusable variables
|
// define reusable variables
|
||||||
const nextStepButton = jQuery('#next_step');
|
const nextStepButton = jQuery('#next_step');
|
||||||
const previousStepButton = jQuery('#return_to_previous');
|
|
||||||
// create a copy of subscribers object for further manipulation
|
// create a copy of subscribers object for further manipulation
|
||||||
const subscribers = jQuery.extend(true, {}, window.importData.step_method_selection);
|
const subscribers = jQuery.extend(true, {}, window.importData.step_method_selection);
|
||||||
const subscribersDataTemplate = Handlebars.compile(jQuery('#subscribers_data_template').html());
|
const subscribersDataTemplate = Handlebars.compile(jQuery('#subscribers_data_template').html());
|
||||||
const subscribersDataTemplatePartial = Handlebars.compile(jQuery('#subscribers_data_template_partial').html());
|
const subscribersDataTemplatePartial = Handlebars.compile(jQuery('#subscribers_data_template_partial').html());
|
||||||
const subscribersDataParseResultsTemplate = Handlebars.compile(jQuery('#subscribers_data_parse_results_template').html());
|
|
||||||
const segmentSelectElement = jQuery('#mailpoet_segments_select');
|
const segmentSelectElement = jQuery('#mailpoet_segments_select');
|
||||||
const maxRowsToShow = 10;
|
const maxRowsToShow = 10;
|
||||||
const filler = '. . .';
|
const filler = '. . .';
|
||||||
@@ -110,8 +32,6 @@ jQuery(document).ready(() => {
|
|||||||
const fillerArray = Array(...new Array(subscribers.subscribers[0].length))
|
const fillerArray = Array(...new Array(subscribers.subscribers[0].length))
|
||||||
.map(String.prototype.valueOf, filler);
|
.map(String.prototype.valueOf, filler);
|
||||||
|
|
||||||
showCurrentStep();
|
|
||||||
|
|
||||||
function toggleNextStepButton(condition) {
|
function toggleNextStepButton(condition) {
|
||||||
const disabled = 'button-disabled';
|
const disabled = 'button-disabled';
|
||||||
if (condition === 'on') {
|
if (condition === 'on') {
|
||||||
@@ -125,61 +45,6 @@ jQuery(document).ready(() => {
|
|||||||
jQuery('#subscribers_data_parse_results:visible').html('');
|
jQuery('#subscribers_data_parse_results:visible').html('');
|
||||||
jQuery('#subscribers_data_import_results:visible').hide();
|
jQuery('#subscribers_data_import_results:visible').hide();
|
||||||
|
|
||||||
// show parse statistics if any duplicate/invalid records were found
|
|
||||||
if (subscribers.invalid.length || subscribers.duplicate.length || subscribers.role.length) {
|
|
||||||
// count repeating e-mails inside duplicate array and present them in
|
|
||||||
// 'email (xN)' format
|
|
||||||
duplicates = {};
|
|
||||||
subscribers.duplicate.forEach((subscriberEmail) => {
|
|
||||||
duplicates[subscriberEmail] = (duplicates[subscriberEmail] || 0) + 1;
|
|
||||||
});
|
|
||||||
subscribers.duplicate = [];
|
|
||||||
Object.keys(duplicates).forEach((email) => {
|
|
||||||
if (duplicates[email] > 1) {
|
|
||||||
subscribers.duplicate.push(`${email} (x${duplicates[email]})`);
|
|
||||||
} else {
|
|
||||||
subscribers.duplicate.push(email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
importResults = {
|
|
||||||
notice: MailPoet.I18n.t('importNoticeSkipped').replace(
|
|
||||||
'%1$s',
|
|
||||||
`<strong>${subscribers.invalid.length + subscribers.duplicate.length + subscribers.role.length}</strong>`
|
|
||||||
),
|
|
||||||
invalid: (subscribers.invalid.length)
|
|
||||||
? MailPoet.I18n.t('importNoticeInvalid')
|
|
||||||
.replace('%1$s', `<strong>${subscribers.invalid.length.toLocaleString()}</strong>`)
|
|
||||||
.replace('%2$s', subscribers.invalid.join(', '))
|
|
||||||
: null,
|
|
||||||
duplicate: (subscribers.duplicate.length)
|
|
||||||
? MailPoet.I18n.t('importNoticeDuplicate')
|
|
||||||
.replace('%1$s', `<strong>${subscribers.duplicate.length}</strong>`)
|
|
||||||
.replace('%2$s', subscribers.duplicate.join(', '))
|
|
||||||
: null,
|
|
||||||
role: (subscribers.role.length)
|
|
||||||
? MailPoet.I18n.t('importNoticeRoleBased')
|
|
||||||
.replace('%1$s', `<strong>${subscribers.role.length.toLocaleString()}</strong>`)
|
|
||||||
.replace('%2$s', subscribers.role.join(', '))
|
|
||||||
.replace(
|
|
||||||
/\[link](.+)\[\/link]/,
|
|
||||||
'<a href="https://kb.mailpoet.com/article/270-role-based-email-addresses-are-not-allowed" target="_blank" rel="noopener noreferrer">$1</a>'
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
jQuery('#subscribers_data_parse_results').html(
|
|
||||||
subscribersDataParseResultsTemplate(importResults)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
jQuery('.mailpoet_subscribers_data_parse_results_details_show')
|
|
||||||
.click(function detailsClick() {
|
|
||||||
const details = jQuery('.mailpoet_subscribers_data_parse_results_details');
|
|
||||||
details.toggle();
|
|
||||||
jQuery(this).text((details.is(':visible'))
|
|
||||||
? MailPoet.I18n.t('hideDetails')
|
|
||||||
: MailPoet.I18n.t('showDetails'));
|
|
||||||
});
|
|
||||||
|
|
||||||
// show available segments
|
// show available segments
|
||||||
if (window.mailpoetSegments.length) {
|
if (window.mailpoetSegments.length) {
|
||||||
@@ -610,12 +475,6 @@ jQuery(document).ready(() => {
|
|||||||
filterSubscribers();
|
filterSubscribers();
|
||||||
});
|
});
|
||||||
|
|
||||||
previousStepButton.off().on('click', () => {
|
|
||||||
router.navigate(
|
|
||||||
getDataManipulationPreviousStepLink(window.importData.step_method_selection),
|
|
||||||
{ trigger: true }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
nextStepButton.off().on('click', (event) => {
|
nextStepButton.off().on('click', (event) => {
|
||||||
const columns = {};
|
const columns = {};
|
||||||
|
@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import PreviousNextStepButtons from './previous_next_step_buttons.jsx';
|
import PreviousNextStepButtons from './previous_next_step_buttons.jsx';
|
||||||
|
import Warnings from './step_data_manipulation/warnings.jsx';
|
||||||
|
|
||||||
function getPreviousStepLink(importData, subscribersLimitForValidation) {
|
function getPreviousStepLink(importData, subscribersLimitForValidation) {
|
||||||
if (importData === undefined) {
|
if (importData === undefined) {
|
||||||
@@ -32,6 +33,9 @@ function StepDataManipulation({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Warnings
|
||||||
|
stepMethodSelectionData={stepMethodSelectionData}
|
||||||
|
/>
|
||||||
<PreviousNextStepButtons
|
<PreviousNextStepButtons
|
||||||
canGoNext={false}
|
canGoNext={false}
|
||||||
onPreviousAction={() => (
|
onPreviousAction={() => (
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import ReactStringReplace from 'react-string-replace';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const getSingleWarning = (warningTranslation, subscribers) => {
|
||||||
|
let warning = '';
|
||||||
|
if (subscribers.length) {
|
||||||
|
warning = ReactStringReplace(
|
||||||
|
warningTranslation.replace('%2$s', subscribers.join(', ')),
|
||||||
|
'%1$s',
|
||||||
|
() => <strong key={warningTranslation}>{subscribers.length.toLocaleString()}</strong>
|
||||||
|
);
|
||||||
|
warning = <p>{warning}</p>;
|
||||||
|
}
|
||||||
|
return warning;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Warnings = ({
|
||||||
|
stepMethodSelectionData,
|
||||||
|
}) => {
|
||||||
|
const { invalid, duplicate, role } = stepMethodSelectionData;
|
||||||
|
|
||||||
|
const [detailsShown, setDetailsShown] = useState(false);
|
||||||
|
|
||||||
|
const detailClasses = classNames(
|
||||||
|
'mailpoet_subscribers_data_parse_results_details',
|
||||||
|
{ mailpoet_hidden: !detailsShown },
|
||||||
|
);
|
||||||
|
|
||||||
|
const invalidWarning = getSingleWarning(MailPoet.I18n.t('importNoticeInvalid'), invalid);
|
||||||
|
|
||||||
|
const duplicateWarning = getSingleWarning(MailPoet.I18n.t('importNoticeDuplicate'), duplicate);
|
||||||
|
|
||||||
|
let roleBasedWarning = '';
|
||||||
|
if (role.length) {
|
||||||
|
roleBasedWarning = ReactStringReplace(
|
||||||
|
MailPoet.I18n.t('importNoticeRoleBased'),
|
||||||
|
/(%1\$s|\[link\].*\[\/link\]|%2\$s)/,
|
||||||
|
(match) => {
|
||||||
|
if (match === '%1$s') return <strong key="role-length">{role.length.toLocaleString()}</strong>;
|
||||||
|
if (match === '%2$s') return role.join(', ');
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://kb.mailpoet.com/article/270-role-based-email-addresses-are-not-allowed"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
key={match}
|
||||||
|
>
|
||||||
|
{match.replace('[link]', '').replace('[/link]', '')}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
roleBasedWarning = <p>{roleBasedWarning}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
invalid.length
|
||||||
|
|| duplicate.length
|
||||||
|
|| role.length
|
||||||
|
) {
|
||||||
|
const allWarningsCount = invalid.length + duplicate.length + role.length;
|
||||||
|
return (
|
||||||
|
<div className="error">
|
||||||
|
<p>
|
||||||
|
{ReactStringReplace(MailPoet.I18n.t('importNoticeSkipped'), '%1$s', () => (
|
||||||
|
<strong key="lengths">{allWarningsCount.toLocaleString()}</strong>
|
||||||
|
))}
|
||||||
|
{' '}
|
||||||
|
<a
|
||||||
|
className="mailpoet_subscribers_data_parse_results_details_show"
|
||||||
|
data-automation-id="show-more-details"
|
||||||
|
onClick={() => setDetailsShown(!detailsShown)}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if ((['keydown', 'keypress'].includes(event.type) && ['Enter', ' '].includes(event.key))
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
setDetailsShown(!detailsShown);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{MailPoet.I18n.t('showMoreDetails')}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div className={detailClasses}>
|
||||||
|
<hr />
|
||||||
|
{invalidWarning}
|
||||||
|
{duplicateWarning}
|
||||||
|
{roleBasedWarning}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Warnings.propTypes = {
|
||||||
|
stepMethodSelectionData: PropTypes.shape({
|
||||||
|
duplicate: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
invalid: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
role: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Warnings.defaultProps = {
|
||||||
|
stepMethodSelectionData: {
|
||||||
|
invalid: [],
|
||||||
|
duplicate: [],
|
||||||
|
role: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Warnings;
|
||||||
|
@@ -105,6 +105,7 @@
|
|||||||
'methodMailChimpVerify': __('Verify'),
|
'methodMailChimpVerify': __('Verify'),
|
||||||
'methodMailChimpSelectList': __('Select list(s)'),
|
'methodMailChimpSelectList': __('Select list(s)'),
|
||||||
'methodMailChimpSelectPlaceholder': _x('Select', 'Verb'),
|
'methodMailChimpSelectPlaceholder': _x('Select', 'Verb'),
|
||||||
|
'showMoreDetails': __('Show more details'),
|
||||||
'pasteLabel': __('Copy and paste your subscribers from Excel/Spreadsheets'),
|
'pasteLabel': __('Copy and paste your subscribers from Excel/Spreadsheets'),
|
||||||
'pasteDescription': __('This file needs to be formatted in a CSV style (comma-separated-values.) Look at some [link]examples on our support site[/link].'),
|
'pasteDescription': __('This file needs to be formatted in a CSV style (comma-separated-values.) Look at some [link]examples on our support site[/link].'),
|
||||||
'methodSelectionHead': __('How would you like to import subscribers?')
|
'methodSelectionHead': __('How would you like to import subscribers?')
|
||||||
|
@@ -3,23 +3,6 @@
|
|||||||
<!-- Template data -->
|
<!-- Template data -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script id="subscribers_data_parse_results_template" type="text/x-handlebars-template">
|
|
||||||
<div class="error">
|
|
||||||
<p>{{{notice}}} <a class="mailpoet_subscribers_data_parse_results_details_show" href="javascript:;" data-automation-id="show-more-details"><%= __('Show more details') %></a><p>
|
|
||||||
<div class="mailpoet_subscribers_data_parse_results_details mailpoet_hidden">
|
|
||||||
<hr>
|
|
||||||
{{#if duplicate}}
|
|
||||||
<p>{{{duplicate}}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if invalid}}
|
|
||||||
<p>{{{invalid}}}</p>
|
|
||||||
{{/if}}
|
|
||||||
{{#if role}}
|
|
||||||
<p>{{{role}}}</p>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="inside">
|
<div class="inside">
|
||||||
<br>
|
<br>
|
||||||
|
Reference in New Issue
Block a user