/** * 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 * @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 { batchSizeCalculator: ImportBatchSizeCalculator; postSizeChecker: PostSizeChecker; defaultBatchSize: number; importUrl: string; totalRowsCount: number; hasWarnings: boolean; hasErrors: boolean; importCancelRequested: boolean; importConfiguration: Record; constructor() { this.configuration = {}; this.importConfiguration = {}; this.progressModal = new ImportProgressModal(); this.batchSizeCalculator = new ImportBatchSizeCalculator(); this.postSizeChecker = new PostSizeChecker(); this.importUrl = ''; this.totalRowsCount = 0; this.hasWarnings = false; this.hasErrors = false; this.importCancelRequested = false; // 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: string, configuration: Record): void { 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 */ private ajaxImport( offset: number, batchSize: number, validateOnly = true, recurringVariables = {}, ): void { 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: string = 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(): void { 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: Record) { this.importConfiguration = importConfiguration; } /** * Get the import configuration. * * @returns {*} */ get configuration(): Record { return this.importConfiguration; } /** * Set progress modal. * * @param {ImportProgressModal} modal */ set progressModal(modal: ImportProgressModal) { this.progressModal = modal; } /** * Get progress modal. * * @returns {ImportProgressModal} */ get progressModal(): ImportProgressModal { return this.progressModal; } /** * Request import cancellation. * Import operation will be cancelled at next iteration when requested. */ requestCancelImport(): void { this.importCancelRequested = true; } /** * Merge given configuration into current import configuration. * * @param {Object} configuration * @private */ private mergeConfiguration(configuration: Record): void { this.importConfiguration = { ...this.importConfiguration, ...configuration, }; } /** * Cancel the import process. * @private */ private cancelImport(): void { this.progressModal.hide(); this.importCancelRequested = false; } /** * Additional actions when import is stopped. * @private */ private onImportStop(): void { this.progressModal.showCloseModalButton(); this.progressModal.hideAbortImportButton(); } /** * Additional actions when import is finished. * @private */ private onImportFinish(): void { this.onImportStop(); this.progressModal.showSuccessMessage(); this.progressModal.setImportedProgressLabel(); this.progressModal.updateProgress(this.totalRowsCount, this.totalRowsCount); } /** * Additional actions when import is starting. * @private */ private onImportStart(): void { // Marking the start of import operation. this.batchSizeCalculator.markImportStart(); this.progressModal.showAbortImportButton(); } }