first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
const {$} = window;
export default class EntityFieldsValidator {
/**
* Validates entity fields
*
* @returns {boolean}
*/
static validate() {
$('.js-validation-error').addClass('d-none');
return this.checkDuplicateSelectedValues() && this.checkRequiredFields();
}
/**
* Checks if there are no duplicate selected values.
*
* @returns {boolean}
* @private
*/
static checkDuplicateSelectedValues() {
const uniqueFields = [];
let valid = true;
$('.js-entity-field select').each(function () {
const value = $(this).val();
if (value === 'no') {
return;
}
if ($.inArray(value, uniqueFields) !== -1) {
valid = false;
$('.js-duplicate-columns-warning').removeClass('d-none');
return;
}
uniqueFields.push(value);
});
return valid;
}
/**
* Checks if all required fields are selected.
*
* @returns {boolean}
* @private
*/
static checkRequiredFields() {
const requiredImportFields = $('.js-import-data-table').data('required-fields');
/* eslint-disable-next-line */
for (const key in requiredImportFields) {
if ($(`option[value="${requiredImportFields[key]}"]:selected`).length === 0) {
$('.js-missing-column-warning').removeClass('d-none');
$('.js-missing-column').text($(`option[value="${requiredImportFields[key]}"]:first`).text());
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
/**
* Class ImportBatchSizeCalculator calculates the import batch size.
* Import batch size is the maximum number of records that
* the import should handle in one batch.
*/
export default class ImportBatchSizeCalculator {
constructor() {
// Target execution time in milliseconds.
this.targetExecutionTime = 5000;
// Maximum batch size increase multiplier.
this.maxAcceleration = 4;
// Minimum and maximum import batch sizes.
this.minBatchSize = 5;
this.maxBatchSize = 100;
}
/**
* Marks the start of the import operation.
* Must be executed before starting the import,
* to be able to calculate the import batch size later on.
*/
markImportStart() {
this.importStartTime = new Date().getTime();
}
/**
* Marks the end of the import operation.
* Must be executed after the import operation finishes,
* to be able to calculate the import batch size later on.
*/
markImportEnd() {
this.actualExecutionTime = new Date().getTime() - this.importStartTime;
}
/**
* Calculates how much the import execution time can be increased to still be acceptable.
*
* @returns {number}
* @private
*/
calculateAcceleration() {
return Math.min(this.maxAcceleration, this.targetExecutionTime / this.actualExecutionTime);
}
/**
* Calculates the recommended import batch size.
*
* @param {number} currentBatchSize current import batch size
* @param {number} maxBatchSize greater than zero, the batch size that shouldn't be exceeded
*
* @returns {number} recommended import batch size
*/
calculateBatchSize(currentBatchSize, maxBatchSize = 0) {
if (!this.importStartTime) {
throw new Error('Import start is not marked.');
}
if (!this.actualExecutionTime) {
throw new Error('Import end is not marked.');
}
const candidates = [
this.maxBatchSize,
Math.max(this.minBatchSize, Math.floor(currentBatchSize * this.calculateAcceleration())),
];
if (maxBatchSize > 0) {
candidates.push(maxBatchSize);
}
return Math.min(...candidates);
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import ImportMatchConfiguration from './ImportMatchConfiguration';
import ImportDataTable from './ImportDataTable';
import EntityFieldsValidator from './EntityFieldsValidator';
import Importer from './Importer';
export default class ImportDataPage {
constructor() {
new ImportMatchConfiguration();
new ImportDataTable();
this.importer = new Importer();
$(document).on('click', '.js-process-import', (e) => this.importHandler(e));
$(document).on('click', '.js-abort-import', () => this.importer.requestCancelImport());
$(document).on('click', '.js-close-modal', () => this.importer.progressModal.hide());
$(document).on('click', '.js-continue-import', () => this.importer.continueImport());
}
/**
* Import process event handler
*/
importHandler(e) {
e.preventDefault();
if (!EntityFieldsValidator.validate()) {
return;
}
const configuration = {};
// Collect the configuration from the form into an array.
$('.import-data-configuration-form').find(
'#skip, select[name^=type_value], #csv, #iso_lang, #entity,'
+ '#truncate, #match_ref, #regenerate, #forceIDs, #sendemail,'
+ '#separator, #multiple_value_separator',
).each((index, $input) => {
configuration[$($input).attr('name')] = $($input).val();
});
this.importer.import(
$('.js-import-process-button').data('import_url'),
configuration,
);
}
}

View File

@@ -0,0 +1,174 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
const {$} = window;
const $importDataTable = $('.js-import-data-table');
/**
* Pagination directions - forward and backward.
*/
const FORWARD = 'forward';
const BACKWARD = 'backward';
export default class ImportDataTable {
constructor() {
this.numberOfColumnsPerPage = this.getNumberOfVisibleColumns();
this.totalNumberOfColumns = this.getTotalNumberOfColumns();
$('.js-import-next-page').on('click', () => this.importNextPageHandler());
$('.js-import-previous-page').on('click', () => this.importPreviousPageHandler());
}
/**
* Handle the next page action in import data table.
*/
importNextPageHandler() {
this.importPaginationHandler(FORWARD);
}
/**
* Handle the previous page action in import data table.
*/
importPreviousPageHandler() {
this.importPaginationHandler(BACKWARD);
}
/**
* Handle the forward and back buttons actions in the import table.
*
* @param {string} direction
* @private
*/
importPaginationHandler(direction) {
const $currentPageElements = $importDataTable.find('th:visible,td:visible');
const $oppositePaginationButton = direction === FORWARD ? $('.js-import-next-page') : $('.js-import-previous-page');
let lastVisibleColumnFound = false;
let numberOfVisibleColumns = 0;
let $tableColumns = $importDataTable.find('th');
if (direction === BACKWARD) {
// If going backward - reverse the table columns array and use the same logic as forward
$tableColumns = $($tableColumns.toArray().reverse());
}
/* eslint-disable-next-line */
for (const index in $tableColumns) {
if (Number.isNaN(index)) {
// Reached the last column - hide the opposite pagination button
this.hide($oppositePaginationButton);
break;
}
// Searching for last visible column
if ($($tableColumns[index]).is(':visible')) {
lastVisibleColumnFound = true;
/* eslint-disable-next-line no-continue */
continue;
}
// If last visible column was found - show the column after it
if (lastVisibleColumnFound) {
// If going backward, the column index must be counted from the last element
const showColumnIndex = direction === BACKWARD ? this.totalNumberOfColumns - 1 - index : index;
this.showTableColumnByIndex(showColumnIndex);
numberOfVisibleColumns += 1;
// If number of visible columns per page is already reached - break the loop
if (numberOfVisibleColumns >= this.numberOfColumnsPerPage) {
this.hide($oppositePaginationButton);
break;
}
}
}
// Hide all the columns from previous page
this.hide($currentPageElements);
// If the first column in the table is not visible - show the "previous" pagination arrow
if (!$importDataTable.find('th:first').is(':visible')) {
this.show($('.js-import-previous-page'));
}
// If the last column in the table is not visible - show the "next" pagination arrow
if (!$importDataTable.find('th:last').is(':visible')) {
this.show($('.js-import-next-page'));
}
}
/**
* Gets the number of currently visible columns in the import data table.
*
* @returns {number}
* @private
*/
getNumberOfVisibleColumns() {
return $importDataTable.find('th:visible').length;
}
/**
* Gets the total number of columns in the import data table.
*
* @returns {number}
* @private
*/
getTotalNumberOfColumns() {
return $importDataTable.find('th').length;
}
/**
* Hide the elements.
*
* @param $elements
* @private
*/
hide($elements) {
$elements.addClass('d-none');
}
/**
* Show the elements.
*
* @param $elements
* @private
*/
show($elements) {
$elements.removeClass('d-none');
}
/**
* Shows a column from import data table by given index
*
* @param columnIndex
* @private
*/
showTableColumnByIndex(columnIndex) {
// Increasing the index because nth-child calculates from 1 and index starts from 0
const colIndex = columnIndex + 1;
this.show($importDataTable.find(`th:nth-child(${colIndex})`));
this.show($importDataTable.find('tbody > tr').find(`td:nth-child(${colIndex})`));
}
}

View File

@@ -0,0 +1,176 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
const {$} = window;
/**
* Class is responsible for import match configuration
* in Advanced parameters -> Import -> step 2 form.
*/
export default class ImportMatchConfiguration {
/**
* Initializes all the processes related with import matches.
*/
constructor() {
this.loadEvents();
}
/**
* Loads all events for data match configuration.
*/
loadEvents() {
$(document).on('click', '.js-save-import-match', (event) => this.save(event));
$(document).on('click', '.js-load-import-match', (event) => this.load(event));
$(document).on('click', '.js-delete-import-match', (event) => this.delete(event));
}
/**
* Save the import match configuration.
*/
save(event) {
event.preventDefault();
const ajaxUrl = $('.js-save-import-match').attr('data-url');
const formData = $('.import-data-configuration-form').serialize();
$.ajax({
type: 'POST',
url: ajaxUrl,
data: formData,
}).then((response) => {
if (typeof response.errors !== 'undefined' && response.errors.length) {
this.showErrorPopUp(response.errors);
} else if (response.matches.length > 0) {
const $dataMatchesDropdown = this.matchesDropdown;
Object.values(response.matches).forEach((resp) => {
const $existingMatch = $dataMatchesDropdown.find(`option[value=${resp.id_import_match}]`);
// If match already exists with same id - do nothing
if ($existingMatch.length > 0) {
return;
}
// Append the new option to the matches dropdown
this.appendOptionToDropdown(
$dataMatchesDropdown,
resp.name,
resp.id_import_match,
);
});
}
});
}
/**
* Load the import match.
*/
load(event) {
event.preventDefault();
const ajaxUrl = $('.js-load-import-match').attr('data-url');
$.ajax({
type: 'GET',
url: ajaxUrl,
data: {
import_match_id: this.matchesDropdown.val(),
},
}).then((response) => {
if (response) {
this.rowsSkipInput.val(response.skip);
const entityFields = response.match.split('|');
Object.keys(entityFields).forEach((i) => {
$(`#type_value_${i}`).val(entityFields[i]);
});
}
});
}
/**
* Delete the import match.
*/
delete(event) {
event.preventDefault();
const ajaxUrl = $('.js-delete-import-match').attr('data-url');
const $dataMatchesDropdown = this.matchesDropdown;
const selectedMatchId = $dataMatchesDropdown.val();
$.ajax({
type: 'DELETE',
url: ajaxUrl,
data: {
import_match_id: selectedMatchId,
},
}).then(() => {
// Delete the match option from matches dropdown
$dataMatchesDropdown.find(`option[value=${selectedMatchId}]`).remove();
});
}
/**
* Appends a new option to given dropdown.
*
* @param {jQuery} $dropdown
* @param {String} optionText
* @param {String} optionValue
* @private
*/
appendOptionToDropdown($dropdown, optionText, optionValue) {
const $newOption = $('<option>');
$newOption.attr('value', optionValue);
$newOption.text(optionText);
$dropdown.append($newOption);
}
/**
* Shows error messages in the native error pop-up.
*
* @param {Array} errors
* @private
*/
showErrorPopUp(errors) {
alert(errors);
}
/**
* Get the matches dropdown.
*
* @returns {*|HTMLElement}
*/
get matchesDropdown() {
return $('#matches');
}
/**
* Get the "rows to skip" input.
*
* @returns {*|HTMLElement}
*/
get rowsSkipInput() {
return $('#skip');
}
}

View File

@@ -0,0 +1,318 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
export default class ImportProgressModal {
/**
* Show the import progress modal window.
*/
show() {
this.progressModal.modal('show');
}
/**
* Hide the import progress modal window.
*/
hide() {
this.progressModal.modal('hide');
}
/**
* Updates the import progressbar.
*
* @param {number} completed number of completed items.
* @param {number} total number of items in total.
*/
updateProgress(completed, total) {
const progressCompleted = parseInt(completed, 10);
const progressTotal = parseInt(total, 10);
const $progressBar = this.progressBar;
const percentage = (progressCompleted / progressTotal) * 100;
$progressBar.css('width', `${percentage}%`);
$progressBar.find('> span').text(`${progressCompleted}/${progressTotal}`);
}
/**
* Updates the progress bar label.
*
* @param {String} label if not provided - will use the default label
*/
updateProgressLabel(label) {
this.progressLabel.text(label);
}
/**
* Sets the progress label to "importing"
*/
setImportingProgressLabel() {
this.updateProgressLabel(this.progressModal.find('.modal-body').data('importing-label'));
}
/**
* Sets the progress label to "imported"
*/
setImportedProgressLabel() {
this.updateProgressLabel(this.progressModal.find('.modal-body').data('imported-label'));
}
/**
* Shows information messages in the import modal.
*
* @param {Array} messages
*/
showInfoMessages(messages) {
this.showMessages(this.infoMessageBlock, messages);
}
/**
* Shows warning messages in the import modal.
*
* @param {Array} messages
*/
showWarningMessages(messages) {
this.showMessages(this.warningMessageBlock, messages);
}
/**
* Shows error messages in the import modal.
*
* @param {Array} messages
*/
showErrorMessages(messages) {
this.showMessages(this.errorMessageBlock, messages);
}
/**
* Shows the import success message.
*/
showSuccessMessage() {
this.successMessageBlock.removeClass('d-none');
}
/**
* Shows the post size limit warning message.
*
* @param {number} postSizeValue to be shown in the warning
*/
showPostLimitMessage(postSizeValue) {
this.postLimitMessage.find('#post_limit_value').text(postSizeValue);
this.postLimitMessage.removeClass('d-none');
}
/**
* Show messages in given message block.
*
* @param {jQuery} $messageBlock
* @param {Array} messages
* @private
*/
showMessages($messageBlock, messages) {
let showMessagesBlock = false;
Object.values(messages).forEach((msg) => {
// Indicate that the messages block should be displayed
showMessagesBlock = true;
const message = $('<div>');
message.text(msg);
message.addClass('message');
$messageBlock.append(message);
});
if (showMessagesBlock) {
$messageBlock.removeClass('d-none');
}
}
/**
* Show the "Ignore warnings and continue" button.
*/
showContinueImportButton() {
this.continueImportButton.removeClass('d-none');
}
/**
* Hide the "Ignore warnings and continue" button.
*/
hideContinueImportButton() {
this.continueImportButton.addClass('d-none');
}
/**
* Show the "Abort import" button.
*/
showAbortImportButton() {
this.abortImportButton.removeClass('d-none');
}
/**
* Hide the "Abort import" button.
*/
hideAbortImportButton() {
this.abortImportButton.addClass('d-none');
}
/**
* Show the "Close" button of the modal.
*/
showCloseModalButton() {
this.closeModalButton.removeClass('d-none');
}
/**
* Hide the "Close" button.
*/
hideCloseModalButton() {
this.closeModalButton.addClass('d-none');
}
/**
* Clears all warning messages from the modal.
*/
clearWarningMessages() {
this.warningMessageBlock.addClass('d-none').find('.message').remove();
}
/**
* Reset the modal - resets progress bar and removes messages.
*/
reset() {
// Reset the progress bar
this.updateProgress(0, 0);
this.updateProgressLabel(this.progressLabel.attr('default-value'));
// Hide action buttons
this.continueImportButton.addClass('d-none');
this.abortImportButton.addClass('d-none');
this.closeModalButton.addClass('d-none');
// Remove messages
this.successMessageBlock.addClass('d-none');
this.infoMessageBlock.addClass('d-none').find('.message').remove();
this.errorMessageBlock.addClass('d-none').find('.message').remove();
this.postLimitMessage.addClass('d-none');
this.clearWarningMessages();
}
/**
* Gets import progress modal.
*
* @returns {jQuery}
*/
get progressModal() {
return $('#import_progress_modal');
}
/**
* Gets import progress bar.
*
* @returns {jQuery}
*/
get progressBar() {
return this.progressModal.find('.progress-bar');
}
/**
* Gets information messages block.
*
* @returns {jQuery|HTMLElement}
*/
get infoMessageBlock() {
return $('.js-import-info');
}
/**
* Gets error messages block.
*
* @returns {jQuery|HTMLElement}
*/
get errorMessageBlock() {
return $('.js-import-errors');
}
/**
* Gets warning messages block.
*
* @returns {jQuery|HTMLElement}
*/
get warningMessageBlock() {
return $('.js-import-warnings');
}
/**
* Gets success messages block.
*
* @returns {jQuery|HTMLElement}
*/
get successMessageBlock() {
return $('.js-import-success');
}
/**
* Gets post limit message.
*
* @returns {jQuery|HTMLElement}
*/
get postLimitMessage() {
return $('.js-post-limit-warning');
}
/**
* Gets "Ignore warnings and continue" button.
*
* @returns {jQuery|HTMLElement}
*/
get continueImportButton() {
return $('.js-continue-import');
}
/**
* Gets "Abort import" button.
*
* @returns {jQuery|HTMLElement}
*/
get abortImportButton() {
return $('.js-abort-import');
}
/**
* Gets "Close" button of the modal.
*
* @returns {jQuery|HTMLElement}
*/
get closeModalButton() {
return $('.js-close-modal');
}
/**
* Gets progress bar label.
*
* @returns {jQuery|HTMLElement}
*/
get progressLabel() {
return $('#import_progress_bar').find('.progress-details-text');
}
}

View File

@@ -0,0 +1,302 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import ImportProgressModal from './ImportProgressModal';
import ImportBatchSizeCalculator from './ImportBatchSizeCalculator';
import PostSizeChecker from './PostSizeChecker';
export default class Importer {
constructor() {
this.configuration = {};
this.progressModal = new ImportProgressModal();
this.batchSizeCalculator = new ImportBatchSizeCalculator();
this.postSizeChecker = new PostSizeChecker();
// Default number of rows in one batch of the import.
this.defaultBatchSize = 5;
}
/**
* Process the import.
*
* @param {String} importUrl url of the controller, processing the import.
* @param {Object} configuration import configuration.
*/
import(importUrl, configuration) {
this.mergeConfiguration(configuration);
this.importUrl = importUrl;
// Total number of rows to be imported.
this.totalRowsCount = 0;
// Flags that mark that there were warnings or errors during import.
this.hasWarnings = false;
this.hasErrors = false;
// Resetting the import progress modal and showing it.
this.progressModal.reset();
this.progressModal.show();
// Starting the import with default batch size, which is adjusted for next iterations.
this.ajaxImport(0, this.defaultBatchSize);
}
/**
* Process the ajax import request.
*
* @param {number} offset row number, from which the import job will start processing data.
* @param {number} batchSize batch size of this import job.
* @param {boolean} validateOnly whether the data should be only validated, if false - the data will be imported.
* @param {Object} recurringVariables variables which are recurring between import batch jobs.
* @private
*/
ajaxImport(offset, batchSize, validateOnly = true, recurringVariables = {}) {
this.mergeConfiguration({
offset,
limit: batchSize,
validateOnly: validateOnly ? 1 : 0,
crossStepsVars: JSON.stringify(recurringVariables),
});
this.onImportStart();
$.post({
url: this.importUrl,
dataType: 'json',
data: this.configuration,
success: (response) => {
if (this.importCancelRequested) {
this.cancelImport();
return false;
}
const hasErrors = response.errors && response.errors.length;
const hasWarnings = response.warnings && response.warnings.length;
const hasNotices = response.notices && response.notices.length;
if (response.totalCount !== undefined && response.totalCount) {
// The total rows count is retrieved only in the first batch response.
this.totalRowsCount = response.totalCount;
}
// Update import progress.
this.progressModal.updateProgress(response.doneCount, this.totalRowsCount);
if (!validateOnly) {
// Set the progress label to "Importing".
this.progressModal.setImportingProgressLabel();
}
// Information messages are not shown during validation.
if (!validateOnly && hasNotices) {
this.progressModal.showInfoMessages(response.notices);
}
if (hasErrors) {
this.hasErrors = true;
this.progressModal.showErrorMessages(response.errors);
// If there are errors and it's not validation step - stop the import.
// If it's validation step - we will show all errors once it finishes.
if (!validateOnly) {
this.onImportStop();
return false;
}
} else if (hasWarnings) {
this.hasWarnings = true;
this.progressModal.showWarningMessages(response.warnings);
}
if (!response.isFinished) {
// Marking the end of import operation.
this.batchSizeCalculator.markImportEnd();
// Calculate next import batch size and offset.
const nextOffset = offset + batchSize;
const nextBatchSize = this.batchSizeCalculator.calculateBatchSize(batchSize, this.totalRowsCount);
// Showing a warning if post size limit is about to be reached.
if (this.postSizeChecker.isReachingPostSizeLimit(response.postSizeLimit, response.nextPostSize)) {
this.progressModal.showPostLimitMessage(
this.postSizeChecker.getRequiredPostSizeInMegabytes(response.nextPostSize),
);
}
// Run the import again for the next batch.
return this.ajaxImport(
nextOffset,
nextBatchSize,
validateOnly,
response.crossStepsVariables,
);
}
// All import batches are finished successfully.
// If it was only validating the import data until this point,
// we have to run the data import now.
if (validateOnly) {
// If errors occurred during validation - stop the import.
if (this.hasErrors) {
this.onImportStop();
return false;
}
if (this.hasWarnings) {
// Show the button to ignore warnings.
this.progressModal.showContinueImportButton();
this.onImportStop();
return false;
}
// Update the progress bar to 100%.
this.progressModal.updateProgress(this.totalRowsCount, this.totalRowsCount);
// Continue with the data import.
return this.ajaxImport(0, this.defaultBatchSize, false);
}
// Import is completely finished.
return this.onImportFinish();
},
error: (XMLHttpRequest, textStatus) => {
let txt = textStatus;
if (txt === 'parsererror') {
txt = 'Technical error: Unexpected response returned by server. Import stopped.';
}
this.onImportStop();
this.progressModal.showErrorMessages([txt]);
},
});
}
/**
* Continue the import when it was stopped.
*/
continueImport() {
if (!this.configuration) {
throw new Error('Missing import configuration. Make sure the import had started before continuing.');
}
this.progressModal.hideContinueImportButton();
this.progressModal.hideCloseModalButton();
this.progressModal.clearWarningMessages();
this.ajaxImport(0, this.defaultBatchSize, false);
}
/**
* Set the import configuration.
*
* @param importConfiguration
*/
set configuration(importConfiguration) {
this.importConfiguration = importConfiguration;
}
/**
* Get the import configuration.
*
* @returns {*}
*/
get configuration() {
return this.importConfiguration;
}
/**
* Set progress modal.
*
* @param {ImportProgressModal} modal
*/
set progressModal(modal) {
this.modal = modal;
}
/**
* Get progress modal.
*
* @returns {ImportProgressModal}
*/
get progressModal() {
return this.modal;
}
/**
* Request import cancellation.
* Import operation will be cancelled at next iteration when requested.
*/
requestCancelImport() {
this.importCancelRequested = true;
}
/**
* Merge given configuration into current import configuration.
*
* @param {Object} configuration
* @private
*/
mergeConfiguration(configuration) {
this.importConfiguration = {...this.importConfiguration, ...configuration};
}
/**
* Cancel the import process.
* @private
*/
cancelImport() {
this.progressModal.hide();
this.importCancelRequested = false;
}
/**
* Additional actions when import is stopped.
* @private
*/
onImportStop() {
this.progressModal.showCloseModalButton();
this.progressModal.hideAbortImportButton();
}
/**
* Additional actions when import is finished.
* @private
*/
onImportFinish() {
this.onImportStop();
this.progressModal.showSuccessMessage();
this.progressModal.setImportedProgressLabel();
this.progressModal.updateProgress(this.totalRowsCount, this.totalRowsCount);
}
/**
* Additional actions when import is starting.
* @private
*/
onImportStart() {
// Marking the start of import operation.
this.batchSizeCalculator.markImportStart();
this.progressModal.showAbortImportButton();
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
export default class PostSizeChecker {
constructor() {
// How close can we get to the post size limit. 0.9 means 90%.
this.postSizeLimitThreshold = 0.9;
}
/**
* Check if given postSizeLimit is reaching the required post size
*
* @param {number} postSizeLimit
* @param {number} requiredPostSize
*
* @returns {boolean}
*/
isReachingPostSizeLimit(postSizeLimit, requiredPostSize) {
return requiredPostSize >= postSizeLimit * this.postSizeLimitThreshold;
}
/**
* Get required post size in megabytes.
*
* @param {number} requiredPostSize
*
* @returns {number}
*/
getRequiredPostSizeInMegabytes(requiredPostSize) {
return parseInt(requiredPostSize / (1024 * 1024), 10);
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import ImportDataPage from './ImportDataPage';
const {$} = window;
$(() => {
new ImportDataPage();
});