mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-01 02:03:22 +00:00
Streamline Product Import UX flow
This commit is contained in:
committed by
Maikel Linke
parent
d726b0a851
commit
c8397024e4
@@ -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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -68,7 +68,7 @@ module ProductImport
|
||||
end
|
||||
|
||||
def supplier_products
|
||||
@processor.supplier_products
|
||||
@processor.andand.supplier_products
|
||||
end
|
||||
|
||||
def total_supplier_products
|
||||
|
||||
1
app/views/admin/product_import/_ams_data.html.haml
Normal file
1
app/views/admin/product_import/_ams_data.html.haml
Normal file
@@ -0,0 +1 @@
|
||||
= admin_inject_json "admin.productImport", "ams_data", @ams_data
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user