Finish basic import workflow

This commit is contained in:
Samuel Georges 2015-07-21 20:45:02 +10:00
parent d9b98bccca
commit a9899ae3c1
5 changed files with 270 additions and 19 deletions

View File

@ -2,7 +2,7 @@
use Str;
use Backend\Classes\ControllerBehavior;
use League\Csv\Writer as CsvWrtier;
use League\Csv\Writer as CsvWriter;
use League\Csv\Reader as CsvReader;
use ApplicationException;
use Exception;
@ -86,7 +86,22 @@ class ImportExportController extends ControllerBehavior
public function onImport()
{
// traceLog(post());
try {
$model = $this->importGetModel();
$matches = post('column_match', []);
$sessionKey = $this->importUploadFormWidget->getSessionKey();
$model->importDataFromColumnMatch($matches, $sessionKey, [
'firstRowTitles' => post('first_row_titles', false)
]);
$this->vars['importResults'] = $model->getResultStats();
}
catch (Exception $ex) {
$this->controller->handleError($ex);
}
return $this->importExportMakePartial('import_result_form');
}
public function onImportLoadForm()
@ -151,7 +166,7 @@ class ImportExportController extends ControllerBehavior
$this->vars['importDbColumns'] = $this->getImportDbColumns();
$this->vars['importFileColumns'] = $this->getImportFileColumns();
// Make these variables to widgets
// Make these variables available to widgets
$this->controller->vars += $this->vars;
}
@ -214,17 +229,9 @@ class ImportExportController extends ControllerBehavior
protected function getImportFilePath()
{
$model = $this->importGetModel();
$file = $model
->import_file()
->withDeferred($this->importUploadFormWidget->getSessionKey())
->first();
if (!$file) {
return null;
}
return $file->getLocalPath();
return $this
->importGetModel()
->getImportFilePath($this->importUploadFormWidget->getSessionKey());
}
public function importIsColumnRequired($columnName)

View File

@ -101,7 +101,12 @@
this.processImport = function () {
var $form = $('#importFileColumns').closest('form')
$form.request('onImport')
$form.request('onImport', {
success: function(data) {
$('#importContainer').html(data.result)
$(document).trigger('render')
}
})
}
}

View File

@ -0,0 +1,102 @@
<?php if (!$this->fatalError): ?>
<div class="modal-body">
<div class="scoreboard">
<div data-control="toolbar">
<div class="scoreboard-item title-value">
<h4>Created</h4>
<p><?= $importResults->created ?></p>
</div>
<div class="scoreboard-item title-value">
<h4>Updated</h4>
<p><?= $importResults->updated ?></p>
</div>
<?php if ($importResults->skippedCount): ?>
<div class="scoreboard-item title-value">
<h4>Skipped</h4>
<p><?= $importResults->skippedCount ?></p>
</div>
<?php endif ?>
<?php if ($importResults->warningCount): ?>
<div class="scoreboard-item title-value">
<h4>Warnings</h4>
<p><?= $importResults->warningCount ?></p>
</div>
<?php endif ?>
<div class="scoreboard-item title-value">
<h4>Errors</h4>
<p><?= $importResults->errorCount ?></p>
</div>
</div>
</div>
<?php if ($importResults->hasMessages): ?>
<?php
$tabs = [
'skipped' => 'Skipped rows',
'warnings' => 'Warnings',
'errors' => 'Errors',
];
if (!$importResults->skippedCount) unset($tabs['skipped']);
if (!$importResults->warningCount) unset($tabs['warnings']);
if (!$importResults->errorCount) unset($tabs['errors']);
?>
<div class="control-tabs secondary-tabs" data-control="tab">
<ul class="nav nav-tabs">
<?php $count = 0; foreach ($tabs as $code => $tab): ?>
<li class="<?= $count++ == 0 ? 'active' : '' ?>">
<a href="#importTab<?= $code ?>">
<?= $tab ?>
</a>
</li>
<?php endforeach ?>
</ul>
<div class="tab-content">
<?php $count = 0; foreach ($tabs as $code => $tab): ?>
<div class="tab-pane <?= $count++ == 0 ? 'active' : '' ?>">
<div class="list-preview">
<div class="control-simplelist is-divided is-scrollable size-small" data-control="simplelist">
<ul>
<?php foreach ($importResults->{$code} as $row => $message): ?>
<li>
<strong>Row <?= $row ?></strong>
- <?= e($message) ?>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
<?php endforeach ?>
</div>
</div>
<?php endif ?>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-success"
data-dismiss="popup">
<?= e(trans('backend::lang.form.complete')) ?>
</button>
</div>
<?php else: ?>
<div class="modal-body">
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.close')) ?>
</button>
</div>
<?php endif ?>

View File

@ -187,6 +187,7 @@ return [
'close' => 'Close',
'confirm' => 'Confirm',
'reload' => 'Reload',
'complete' => 'Complete',
'ok' => 'OK',
'or' => 'or',
'confirm_tab_close' => 'Do you really want to close the tab? Unsaved changes will be lost.',

View File

@ -1,6 +1,7 @@
<?php namespace Backend\Models;
use Model;
use League\Csv\Reader as CsvReader;
/**
* Model used for importing data
@ -10,17 +11,152 @@ use Model;
*/
abstract class ImportModel extends Model
{
use \October\Rain\Database\Traits\Validation;
/**
* Relations
*/
/**
* The attributes that aren't mass assignable.
* @var array
*/
protected $guarded = [];
/**
* Relations
*/
public $attachOne = [
'import_file' => ['System\Models\File']
];
protected $resultStats = [
'updated' => 0,
'created' => 0,
'errors' => [],
'warnings' => [],
'skipped' => []
];
/**
* Called when data is being imported.
*/
abstract public function importData();
abstract public function importData($results, $sessionKey = null);
public function importDataFromColumnMatch($matches, $sessionKey = null, $options = [])
{
$path = $this->getImportFilePath($sessionKey);
$data = $this->processImportData($path, $matches, $options);
return $this->importData($data, $sessionKey);
}
/**
* Converts column index to database column map to an array containing
* database column names and values pulled from the CSV file.
* Eg:
* [0 => [first_name], 1 => [last_name]]
*
* Will return:
* [first_name => Joe, last_name => Blogs],
* [first_name => Harry, last_name => Potter],
* [...]
*
* @return array
*/
protected function processImportData($filePath, $matches, $options)
{
extract(array_merge([
'firstRowTitles' => true
], $options));
$reader = CsvReader::createFromPath($filePath);
if ($firstRowTitles) {
$reader->setOffset(1);
}
$result = [];
$contents = $reader->fetchAll();
foreach ($contents as $row) {
$result[] = $this->processImportRow($row, $matches);
}
return $result;
}
/**
* Converts a single row of CSV data to the column map.
* @return array
*/
protected function processImportRow($rowData, $matches)
{
$newRow = [];
foreach ($matches as $columnIndex => $dbNames) {
$value = array_get($rowData, $columnIndex);
foreach ((array) $dbNames as $dbName) {
$newRow[$dbName] = $value;
}
}
return $newRow;
}
/**
* Returns an attached imported file local path, if available.
* @return string
*/
public function getImportFilePath($sessionKey = null)
{
$file = $this
->import_file()
->withDeferred($sessionKey)
->first();
if (!$file) {
return null;
}
return $file->getLocalPath();
}
//
// Result logging
//
public function getResultStats()
{
$this->resultStats['errorCount'] = count($this->resultStats['errors']);
$this->resultStats['warningCount'] = count($this->resultStats['warnings']);
$this->resultStats['skippedCount'] = count($this->resultStats['skipped']);
$this->resultStats['hasMessages'] = (
$this->resultStats['errorCount'] > 0 ||
$this->resultStats['warningCount'] > 0 ||
$this->resultStats['skippedCount'] > 0
);
return (object) $this->resultStats;
}
protected function logUpdated()
{
$this->resultStats['updated']++;
}
protected function logCreated()
{
$this->resultStats['created']++;
}
protected function logError($rowIndex, $message)
{
$this->resultStats['errors'][$rowIndex] = $message;
}
protected function logWarning($rowIndex, $message)
{
$this->resultStats['warnings'][$rowIndex] = $message;
}
protected function logSkipped($rowIndex, $message)
{
$this->resultStats['skipped'][$rowIndex] = $message;
}
}