Streamline Product Import UX flow

This commit is contained in:
Matt-Yorkley
2018-07-26 15:55:42 +01:00
committed by Maikel Linke
parent d726b0a851
commit c8397024e4
8 changed files with 79 additions and 119 deletions

View File

@@ -1,20 +1,22 @@
angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $http, $filter, ProductImportService, $timeout) ->
angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $http, $filter, ProductImportService, ams_data, $timeout) ->
$scope.entries = {}
$scope.update_counts = {}
$scope.reset_counts = {}
$scope.importSettings = null
$scope.supplier_product_counts = ams_data.supplier_product_counts
$scope.updates = {}
$scope.updated_total = 0
$scope.updated_ids = []
$scope.update_errors = []
$scope.step = 'settings'
$scope.chunks = 0
$scope.completed = 0
$scope.percentage = "0%"
$scope.started = false
$scope.finished = false
$scope.percentage = {
import: "0%",
save: "0%"
}
$scope.countResettable = () ->
angular.forEach $scope.supplier_product_counts, (value, key) ->
@@ -25,7 +27,6 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.resetProgress = () ->
$scope.chunks = 0
$scope.completed = 0
$scope.percentage = "0%"
$scope.started = false
$scope.finished = false
@@ -33,22 +34,23 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.confirmSettings = () ->
$scope.step = 'import'
$scope.start()
$scope.viewResults = () ->
$scope.countResettable()
$scope.step = 'results'
$scope.resetProgress()
$scope.acceptResults = () ->
$scope.resetProgress()
$scope.step = 'save'
$scope.start()
$scope.finalResults = () ->
$scope.step = 'complete'
$scope.start = () ->
$scope.started = true
$scope.percentage = "1%"
total = $scope.item_count
total = ams_data.item_count
size = 100
$scope.chunks = Math.ceil(total / size)
@@ -64,15 +66,14 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
i++
$scope.processImport = (start, end) ->
$scope.getSettings() if $scope.importSettings == null
$http(
url: $scope.import_url
url: ams_data.import_url
method: 'POST'
data:
'start': start
'end': end
'filepath': $scope.filepath
'settings': $scope.importSettings
'filepath': ams_data.filepath
'settings': ams_data.importSettings
).success((data, status) ->
angular.merge($scope.entries, angular.fromJson(data['entries']))
$scope.sortUpdates(data['reset_counts'])
@@ -83,12 +84,6 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
console.error(data)
)
$scope.getSettings = () ->
$scope.importSettings = {
reset_all_absent: document.getElementsByName('settings[reset_all_absent]')[0].value,
import_into: document.getElementsByName('settings[import_into]')[0].value
}
$scope.sortUpdates = (data) ->
angular.forEach data, (value, key) ->
if (key in $scope.update_counts)
@@ -97,15 +92,14 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.update_counts[key] = value['updates_count']
$scope.processSave = (start, end) ->
$scope.getSettings() if $scope.importSettings == null
$http(
url: $scope.save_url
url: ams_data.save_url
method: 'POST'
data:
'start': start
'end': end
'filepath': $scope.filepath
'settings': $scope.importSettings
'filepath': ams_data.filepath
'settings': ams_data.importSettings
).success((data, status) ->
$scope.sortResults(data['results'])
@@ -131,7 +125,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.updated_total += value
$scope.resetAbsent = () ->
return unless $scope.importSettings['reset_all_absent']
return unless ams_data.importSettings['reset_all_absent']
enterprises_to_reset = []
angular.forEach $scope.reset_counts, (count, enterprise_id) ->
@@ -139,11 +133,11 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
if enterprises_to_reset.length && $scope.updated_ids.length
$http(
url: $scope.reset_url
url: ams_data.reset_url
method: 'POST'
data:
'filepath': $scope.filepath
'settings': $scope.importSettings
'filepath': ams_data.filepath
'settings': ams_data.importSettings
'reset_absent': true,
'updated_ids': $scope.updated_ids,
'enterprises_to_reset': enterprises_to_reset
@@ -155,8 +149,10 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt
$scope.updateProgress = () ->
$scope.completed++
$scope.percentage = String(Math.round(($scope.completed / $scope.chunks) * 100)) + '%'
$scope.percentage[$scope.step] = String(Math.round(($scope.completed / $scope.chunks) * 100)) + '%'
if $scope.completed == $scope.chunks
$scope.finished = true
$timeout($scope.viewResults, 1000) if $scope.step == 'import'
$timeout($scope.finalResults, 1000) if $scope.step == 'save'
$scope.resetAbsent() if $scope.step == 'save'

View File

@@ -232,29 +232,35 @@ form.product-import, div.post-save-results, div.import-wrapper {
}
}
form.product-import, div.save-results {
transition: all linear 0.25s;
}
form.product-import.ng-hide, div.save-results.ng-hide {
opacity: 0;
}
div.import-wrapper {
.ng-hide:not(.ng-hide-animate) {
// We have to use !important here to override angular's display properties
// scss-lint:disable ImportantRule
display: block !important;
position: absolute;
opacity: 0;
top: -9999px;
left: -9999px;
}
.ng-hide-add, .ng-hide-remove, .ng-hide-animate {
transition: all .4s ease-in-out;
}
form.product-import, div.save-results {
transition: all .4s ease-in-out;
}
div.progress-interface {
text-align: center;
transition: all linear 0.25s;
transition: all .4s ease-in-out;
button:disabled {
background: #ccc !important;
}
}
}
div.progress-interface.ng-hide {
position: absolute;
width: 100%;
opacity: 0;
}
.post-save-results {
a.button{
float: left;
@@ -279,7 +285,7 @@ div.progress-bar {
height: 100%;
border-radius: 0.3em;
box-shadow: inset 0 0 3px rgba(0,0,0,0.3);
transition: width 0.5s ease-in-out;
transition: width .3s ease-in-out;
}
}

View File

@@ -11,7 +11,6 @@ module Admin
end
def import
# Save uploaded file to tmp directory
@filepath = save_uploaded_file(params[:file])
@importer = ProductImport::ProductImporter.new(File.new(@filepath), spree_current_user, params[:settings])
@original_filename = params[:file].try(:original_filename)
@@ -19,8 +18,7 @@ module Admin
check_file_errors @importer
check_spreadsheet_has_data @importer
@tax_categories = Spree::TaxCategory.order('is_default DESC, name ASC')
@shipping_categories = Spree::ShippingCategory.order('name ASC')
@ams_data = ams_data
end
def validate_data
@@ -87,6 +85,18 @@ module Admin
end
end
def ams_data
{
filepath: @filepath,
item_count: @importer.item_count,
supplier_product_counts: @importer.supplier_products,
import_url: main_app.admin_product_import_process_async_path,
save_url: main_app.admin_product_import_save_async_path,
reset_url: main_app.admin_product_import_reset_async_path,
importSettings: @importer.import_settings,
}
end
# Define custom model class for Cancan permissions
def model_class
ProductImport::ProductImporter

View File

@@ -68,7 +68,7 @@ module ProductImport
end
def supplier_products
@processor.supplier_products
@processor.andand.supplier_products
end
def total_supplier_products

View File

@@ -0,0 +1 @@
= admin_inject_json "admin.productImport", "ams_data", @ams_data

View File

@@ -1,14 +1,12 @@
- content_for :page_title do
#{t('admin.product_import.title')}
= render partial: 'ams_data'
= render partial: 'spree/admin/shared/product_sub_menu'
.import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl', init: "supplier_product_counts = #{@importer.supplier_products.to_json}"}}
.import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl'}}
= hidden_field_tag "settings[reset_all_absent]", @importer.import_settings['reset_all_absent'], 'ng-model' => "importSettings[reset_all_absent]"
= hidden_field_tag "settings[import_into]", @importer.import_settings['import_into'], 'ng-model' => "importSettings[import_into]"
- if @importer.item_count == 0 #and @importer.invalid_count
- if @importer.item_count == 0
%h5
= t('.no_valid_entries')
%p
@@ -19,20 +17,16 @@
= render 'import_options' if @importer.table_headings
%br
%a.button.proceed{href: '', ng: {click: 'confirmSettings()'}}
= t('.proceed')
= t('.import')
%a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')}
.progress-interface{ng: {show: 'step == "import"'}}
%span.filename
= @original_filename
%span.percentage
({{ percentage }})
({{ percentage.import }})
.progress-bar
%span.progress-track{class: 'ng-binding', style: "width:{{percentage}}"}
%button.start_import{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; import_url = '#{main_app.admin_product_import_process_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}}
= t('.import')
%button.review{ng: {click: 'viewResults()', disabled: '!finished'}}
= t('.review')
%span.progress-track{class: 'ng-binding', style: "width:{{ percentage.import }}"}
%p.red
{{ exception }}
@@ -53,7 +47,8 @@
= hidden_field_tag :filepath, @filepath
= hidden_field_tag "settings[import_into]", @import_into
%a.button.proceed{href: '', ng: {show: 'count((entries | entriesFilterValid:"invalid")) == 0', click: 'acceptResults()'}}= t('.proceed')
%a.button.proceed{href: '', ng: {show: 'count((entries | entriesFilterValid:"invalid")) == 0', click: 'acceptResults()'}}
= t('.save')
%a.button{href: main_app.admin_product_import_path}= t('admin.cancel')
@@ -63,13 +58,9 @@
.progress-interface{ng: {show: 'step == "save"'}}
%span.filename
#{t('.save_imported')} ({{ percentage }})
#{t('.save_imported')} ({{ percentage.save }})
.progress-bar{}
%span.progress-track{ng: {style: "{'width':percentage}"}}
%button.start_save{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; save_url = '#{main_app.admin_product_import_save_async_path}'; reset_url = '#{main_app.admin_product_import_reset_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}}
= t('.save')
%button.view_results{ng: {click: 'finalResults()', disabled: '!finished'}}
= t('.results')
%span.progress-track{ng: {style: "{'width': percentage.save }"}}
%p.red
{{ exception }}

View File

@@ -481,7 +481,7 @@ en:
shipping_categories: Shipping Categories
import:
review: Review
proceed: Proceed
import: Import
save: Save
results: Results
save_imported: Save imported products

View File

@@ -45,9 +45,6 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector '.item-count', text: "2"
@@ -55,9 +52,6 @@ feature "Product Import", js: true do
expect(page).to have_selector '.create-count', text: "2"
expect(page).to_not have_selector '.update-count'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
save_data
expect(page).to have_selector '.created-count', text: '2'
@@ -94,9 +88,6 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector '.item-count', text: "2"
@@ -120,18 +111,12 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector '.item-count', text: "1"
expect(page).to have_selector '.create-count', text: "1"
expect(page).to_not have_selector '.update-count'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
save_data
expect(page).to have_selector '.created-count', text: '1'
@@ -156,14 +141,8 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
save_data
carrots = Spree::Product.find_by_name('Carrots')
@@ -210,14 +189,8 @@ feature "Product Import", js: true do
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
save_data
expect(page).to have_selector '.created-count', text: '1'
@@ -242,9 +215,6 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_selector '.item-count', text: "3"
@@ -254,9 +224,6 @@ feature "Product Import", js: true do
expect(page).to have_selector '.inv-create-count', text: "2"
expect(page).to have_selector '.inv-update-count', text: "1"
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
save_data
expect(page).to_not have_selector '.created-count'
@@ -345,9 +312,6 @@ feature "Product Import", js: true do
attach_file 'file', '/tmp/test.csv'
click_button 'Upload'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link 'Proceed'
import_data
expect(page).to have_content I18n.t('admin.product_import.import.validation_overview')
@@ -363,24 +327,16 @@ feature "Product Import", js: true do
private
def import_data
expect(page).to have_selector 'button.start_import', visible: true
expect(page).to have_selector "button.review[disabled='disabled']"
find('button.start_import').trigger 'click'
wait_until { page.find("button.review:not([disabled='disabled'])").present? }
find('button.review').trigger 'click'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link I18n.t('admin.product_import.import.import')
expect(page).to have_selector 'form.product-import', visible: true
expect(page).to have_content I18n.t('admin.product_import.import.validation_overview')
end
def save_data
expect(page).to have_selector 'button.start_save', visible: true
expect(page).to have_selector "button.view_results[disabled='disabled']"
find('button.start_save').trigger 'click'
wait_until { page.find("button.view_results:not([disabled='disabled'])").present? }
find('button.view_results').trigger 'click'
expect(page).to have_selector 'a.button.proceed', visible: true
click_link I18n.t('admin.product_import.import.save')
expect(page).to have_selector 'div.save-results', visible: true
expect(page).to have_content I18n.t('admin.product_import.save_results.final_results')
end
end