para 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/para/admin/{importers.coffee → job-tracker.coffee} +10 -9
- data/app/controllers/para/admin/crud_resources_controller.rb +0 -14
- data/app/controllers/para/admin/exports_controller.rb +33 -0
- data/app/controllers/para/admin/imports_controller.rb +2 -21
- data/app/controllers/para/admin/jobs_controller.rb +66 -0
- data/app/helpers/para/admin/base_helper.rb +9 -8
- data/app/helpers/para/application_helper.rb +1 -1
- data/app/helpers/para/translations_helper.rb +25 -0
- data/app/views/admin/para/exporter/bases/_completed.html.haml +11 -0
- data/app/views/para/admin/{imports → jobs}/_completed.html.haml +3 -3
- data/app/views/para/admin/{imports → jobs}/_failed.html.haml +1 -1
- data/app/views/para/admin/jobs/_progress.html.haml +5 -0
- data/app/views/para/admin/jobs/show.html.haml +10 -0
- data/app/views/para/admin/resources/_exports_menu.html.haml +15 -8
- data/app/views/para/admin/resources/_imports_menu.html.haml +2 -2
- data/config/locales/fr.yml +19 -10
- data/lib/para/component/exportable.rb +10 -12
- data/lib/para/exporter/base.rb +27 -25
- data/lib/para/exporter/csv.rb +10 -15
- data/lib/para/exporter.rb +0 -50
- data/lib/para/importer/base.rb +7 -10
- data/lib/para/job/base.rb +53 -0
- data/lib/para/job.rb +8 -0
- data/lib/para/version.rb +1 -1
- data/lib/para.rb +1 -0
- data/lib/rails/routing_mapper.rb +5 -1
- data/lib/tasks/para_tasks.rake +3 -3
- metadata +14 -9
- data/app/helpers/para/exports_helper.rb +0 -11
- data/app/views/para/admin/imports/_progress.html.haml +0 -5
- data/app/views/para/admin/imports/show.html.haml +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f79b2e1b6163469c9247f53880b82b50527729e2
|
4
|
+
data.tar.gz: 46444b157efef7ace8f76a566dbcca7f90b62e1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 008274f36acb1819e3ac4b37d74eb115806c7c86d8a5e5f409e98bc568c788ce31e8e4019a412eeb1b86f11025805e7f2f47b9bb609f3e5a4994bebd733e176c
|
7
|
+
data.tar.gz: a08c53ae52881b5ce1d80a2bb951f089db476824802adea8b2e006165164063512b0f2bc660eaa309cd692a8b9a7ea2aee05a740dc81355e6628fbe9c04d453e
|
@@ -1,23 +1,24 @@
|
|
1
|
-
class Para.
|
1
|
+
class Para.JobTracker extends RemoteModalForm
|
2
2
|
initialize: (options = {}) ->
|
3
3
|
super(options)
|
4
4
|
@refreshOnClose = false
|
5
|
+
@trackProgress()
|
5
6
|
|
6
7
|
formSuccess: (e, response) ->
|
7
8
|
super(e, response)
|
8
|
-
|
9
|
-
if ($progressBar = @$el.find('[data-async-progress]')).length
|
10
|
-
@trackProgress($progressBar)
|
9
|
+
@trackProgress()
|
11
10
|
|
12
11
|
trackProgress: ($progressBar) ->
|
13
|
-
|
14
|
-
|
12
|
+
return unless ($progressBar = @$el.find('[data-async-progress]')).length
|
13
|
+
|
14
|
+
@jobStatusURL = @$el.data('job-status-url')
|
15
|
+
@progress = new Para.AsyncProgress(el: $progressBar, progressUrl: @jobStatusURL)
|
15
16
|
@listenTo(@progress, 'completed', @onImportComplete)
|
16
17
|
@listenTo(@progress, 'failed', @onImportComplete)
|
17
18
|
|
18
19
|
onImportComplete: ->
|
19
20
|
$.ajax(
|
20
|
-
url: @
|
21
|
+
url: @jobStatusURL
|
21
22
|
# Force HTTP ACCEPT header to HTML since Rails treats XHR request without
|
22
23
|
# a specific ACCEPT header as JS or JSON by defaut.
|
23
24
|
accepts:
|
@@ -34,5 +35,5 @@ class Para.Importer extends RemoteModalForm
|
|
34
35
|
@progress?.stop()
|
35
36
|
|
36
37
|
$(document).on 'page:change turbolinks:load', ->
|
37
|
-
$('body').on 'ajax:success', '[data-
|
38
|
-
new Para.
|
38
|
+
$('body').on 'ajax:success', '[data-job-tracker-button]', (e, response) ->
|
39
|
+
new Para.JobTracker(modalMarkup: response, $link: $(e.currentTarget))
|
@@ -48,20 +48,6 @@ module Para
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def export
|
52
|
-
if @component.exportable?
|
53
|
-
exporter = Para::Exporter.for(
|
54
|
-
resource_model.name, params[:format]
|
55
|
-
).new(@component.resources.search(params[:q]).result.uniq)
|
56
|
-
|
57
|
-
send_data exporter.render, type: exporter.mime_type,
|
58
|
-
disposition: exporter.disposition,
|
59
|
-
filename: exporter.file_name
|
60
|
-
else
|
61
|
-
redirect_to @component.path
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
51
|
private
|
66
52
|
|
67
53
|
def resource_model
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Para
|
2
|
+
module Admin
|
3
|
+
class ExportsController < Para::Admin::JobsController
|
4
|
+
layout false
|
5
|
+
|
6
|
+
before_action :load_exporter
|
7
|
+
|
8
|
+
def create
|
9
|
+
job = @exporter.perform_later(
|
10
|
+
model_name: @component.model.name,
|
11
|
+
search: params[:q]
|
12
|
+
)
|
13
|
+
|
14
|
+
track_job(job)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def load_exporter
|
20
|
+
exporter_name = params[:exporter]
|
21
|
+
|
22
|
+
@exporter = @component.exporters.find do |exporter|
|
23
|
+
exporter.name == exporter_name
|
24
|
+
end
|
25
|
+
|
26
|
+
unless @exporter
|
27
|
+
raise "Requested exporter (#{ exporter_name }) not found for " +
|
28
|
+
":#{ @component.identifier } component."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,28 +1,10 @@
|
|
1
1
|
module Para
|
2
2
|
module Admin
|
3
|
-
class ImportsController < Para::Admin::
|
4
|
-
include Para::Admin::ResourceControllerConcerns
|
5
|
-
|
3
|
+
class ImportsController < Para::Admin::JobsController
|
6
4
|
layout false
|
7
5
|
|
8
6
|
before_action :load_importer
|
9
7
|
|
10
|
-
def show
|
11
|
-
@status = ActiveJob::Status.get(params[:id])
|
12
|
-
|
13
|
-
respond_to do |format|
|
14
|
-
format.json do
|
15
|
-
if @status.failed?
|
16
|
-
render json: { status: @status.status }, status: 422
|
17
|
-
else
|
18
|
-
render json: { status: @status.status, progress: @status.progress * 100 }
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
format.html
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
8
|
def new
|
27
9
|
@file = Para::Library::File.new
|
28
10
|
@model = resource_model
|
@@ -33,9 +15,8 @@ module Para
|
|
33
15
|
|
34
16
|
if @file.save
|
35
17
|
job = @importer.perform_later(@file)
|
36
|
-
@status = ActiveJob::Status.get(job)
|
37
18
|
|
38
|
-
|
19
|
+
track_job(job)
|
39
20
|
else
|
40
21
|
render 'new'
|
41
22
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# This serves as the base controller class to create simple async ActiveJob
|
2
|
+
# tracking modals, allowing easy job launching and tracking interfaces
|
3
|
+
# implementation in Para and plugins.
|
4
|
+
#
|
5
|
+
# To use this controller, inherit from it in your app, call perform_async on an
|
6
|
+
# ActiveJob class and pass the returned job to the `#track_job(job)` method.
|
7
|
+
#
|
8
|
+
# On the client side, it is advised to use the job-tracker javascript
|
9
|
+
# plugin included into para, that will handle modal displaying and job
|
10
|
+
# status tracking automatically with Ajax requests (ActionCable may come later).
|
11
|
+
# Use a remote link or form, and add the [data-job-tracker-button] attribute,
|
12
|
+
# which will immediately handle the ajax response and display the resulting
|
13
|
+
# modal.
|
14
|
+
#
|
15
|
+
# This will render a modal and the javascript will automatically start tracking
|
16
|
+
# the job progress and refresh the view with progression, success and error
|
17
|
+
# informations
|
18
|
+
#
|
19
|
+
# Example :
|
20
|
+
#
|
21
|
+
# class StatsGenerationController < Para::Admin::JobsController
|
22
|
+
# def run
|
23
|
+
# job = StatsGeneration.perform_async
|
24
|
+
# track_job(job)
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
module Para
|
29
|
+
module Admin
|
30
|
+
class JobsController < Para::Admin::ComponentController
|
31
|
+
include Para::Admin::ResourceControllerConcerns
|
32
|
+
|
33
|
+
def show
|
34
|
+
@status = ActiveJob::Status.get(params[:id])
|
35
|
+
|
36
|
+
respond_to do |format|
|
37
|
+
format.json do
|
38
|
+
if @status.failed?
|
39
|
+
render json: { status: @status.status }, status: 422
|
40
|
+
else
|
41
|
+
render json: { status: @status.status, progress: @status.progress * 100 }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
format.html do
|
46
|
+
@job = @status[:job_type].constantize.new
|
47
|
+
# Assign job id to allow status to be retrieved, which in our case
|
48
|
+
# allows data persistence though the `#store` method
|
49
|
+
@job.job_id = params[:id]
|
50
|
+
|
51
|
+
render layout: false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def track_job(job)
|
59
|
+
@job = job
|
60
|
+
@status = ActiveJob::Status.get(@job)
|
61
|
+
|
62
|
+
render 'show', layout: false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -4,15 +4,16 @@ module Para
|
|
4
4
|
include Para::ApplicationHelper
|
5
5
|
|
6
6
|
def find_partial_for(relation, partial, partial_dir: 'admin/resources')
|
7
|
-
relation_class = if relation.
|
7
|
+
relation_class = if model?(relation.class)
|
8
8
|
relation = relation.class
|
9
9
|
elsif model?(relation)
|
10
10
|
relation
|
11
11
|
end
|
12
12
|
|
13
13
|
relation_name = find_relation_name_for(
|
14
|
-
|
15
|
-
relation_class: relation_class
|
14
|
+
plural_file_path_for(relation), partial,
|
15
|
+
relation_class: relation_class,
|
16
|
+
overrides_root: 'admin'
|
16
17
|
)
|
17
18
|
|
18
19
|
if relation_name
|
@@ -22,8 +23,8 @@ module Para
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
def find_relation_name_for(
|
26
|
-
return relation if partial_exists?(relation, partial)
|
26
|
+
def find_relation_name_for(relation, partial, options = {})
|
27
|
+
return relation if partial_exists?(relation, partial, options)
|
27
28
|
return nil unless options[:relation_class]
|
28
29
|
|
29
30
|
relation = options[:relation_class].ancestors.find do |ancestor|
|
@@ -31,7 +32,7 @@ module Para
|
|
31
32
|
break if ancestor == ActiveRecord::Base
|
32
33
|
|
33
34
|
ancestor_name = plural_file_path_for(ancestor.name)
|
34
|
-
partial_exists?(ancestor_name, partial)
|
35
|
+
partial_exists?(ancestor_name, partial, options)
|
35
36
|
end
|
36
37
|
|
37
38
|
plural_file_path_for(relation) if relation
|
@@ -88,10 +89,10 @@ module Para
|
|
88
89
|
object.respond_to?(:model_name)
|
89
90
|
end
|
90
91
|
|
91
|
-
def partial_exists?(relation, partial)
|
92
|
+
def partial_exists?(relation, partial, overrides_root: 'admin', **options)
|
92
93
|
partial_path = partial.to_s.split('/')
|
93
94
|
partial_path[-1] = "_#{ partial_path.last }"
|
94
|
-
lookup_context.find_all("
|
95
|
+
lookup_context.find_all("#{ overrides_root }/#{relation}/#{ partial_path.join('/') }").any?
|
95
96
|
end
|
96
97
|
end
|
97
98
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Para
|
2
|
+
module TranslationsHelper
|
3
|
+
# This helper method allows to use ActiveModel or ActiveRecord model
|
4
|
+
# hierarchy to use translations with automatic defaults from parent models.
|
5
|
+
#
|
6
|
+
# This works by scanning all the model ancestors to find an existing
|
7
|
+
# translation, allowing defining parent class translations and optionnaly
|
8
|
+
# overriding translations in subclasses scope
|
9
|
+
#
|
10
|
+
def model_translate(key, model: nil, scope: nil, **options)
|
11
|
+
# Get model class if model argument was passed a model instance
|
12
|
+
model = model.class if model.class.respond_to?(:model_name)
|
13
|
+
|
14
|
+
# Create a key for every parent class that could contain a translation
|
15
|
+
defaults = model.lookup_ancestors.map do |klass|
|
16
|
+
:"#{ scope }.#{ klass.model_name.i18n_key }.#{ key }"
|
17
|
+
end
|
18
|
+
|
19
|
+
defaults << options.delete(:default) if options[:default]
|
20
|
+
options[:default] = defaults
|
21
|
+
|
22
|
+
I18n.translate(defaults.shift, options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
= modal.body do
|
2
|
+
.alert.alert-success
|
3
|
+
= model_translate(:success, scope: "para.jobs", model: job)
|
4
|
+
|
5
|
+
= javascript_tag do
|
6
|
+
:plain
|
7
|
+
window.location.href = '#{ job.file.attachment.url }';
|
8
|
+
|
9
|
+
= modal.footer do
|
10
|
+
%button.btn.btn-default{ data: { dismiss: 'modal' } }
|
11
|
+
= t('para.shared.close')
|
@@ -1,7 +1,7 @@
|
|
1
1
|
= modal.body do
|
2
|
-
- if status[:errors].any?
|
2
|
+
- if status[:errors].try(:any?)
|
3
3
|
.alert.alert-warning
|
4
|
-
=
|
4
|
+
= model_translate(:success_with_errors, scope: "para.jobs", model: job)
|
5
5
|
|
6
6
|
%ul
|
7
7
|
- status[:errors].each do |message|
|
@@ -9,7 +9,7 @@
|
|
9
9
|
|
10
10
|
- else
|
11
11
|
.alert.alert-success
|
12
|
-
=
|
12
|
+
= model_translate(:success, scope: "para.jobs", model: job)
|
13
13
|
|
14
14
|
= modal.footer do
|
15
15
|
%button.btn.btn-default{ data: { dismiss: 'modal' } }
|
@@ -0,0 +1,5 @@
|
|
1
|
+
= modal.body do
|
2
|
+
%p= model_translate(:progressing, scope: "para.jobs", model: job)
|
3
|
+
|
4
|
+
.progress{ data: { :'async-progress' => 'import' } }
|
5
|
+
.progress-bar.progress-bar-striped.active{ role: "progressbar", style: "width: #{ status.progress.nan? ? 100 : (status.progress * 100) }%" }
|
@@ -0,0 +1,10 @@
|
|
1
|
+
= modal id: "job-#{ @job.model_name.route_key }", data: { :'job-status-url' => url_for(action: :show, id: @status.job_id) } do |modal|
|
2
|
+
= modal.header do
|
3
|
+
= @job.model_name.human
|
4
|
+
|
5
|
+
- if @status.failed?
|
6
|
+
= render partial: find_partial_for(@job, :failed, partial_dir: 'admin/jobs'), locals: { modal: modal, job: @job }
|
7
|
+
- elsif @status.completed?
|
8
|
+
= render partial: find_partial_for(@job, :completed, partial_dir: 'admin/jobs'), locals: { modal: modal, job: @job, status: @status }
|
9
|
+
- else
|
10
|
+
= render partial: find_partial_for(@job, :progress, partial_dir: 'admin/jobs'), locals: { modal: modal, job: @job, status: @status }
|
@@ -1,12 +1,19 @@
|
|
1
1
|
- if component.exportable?
|
2
|
-
- component.
|
2
|
+
- if component.exporters.length == 1
|
3
|
+
- exporter = component.exporters.first
|
4
|
+
|
5
|
+
= link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q]), class: 'btn btn-default', method: :post, remote: true, data: { :'job-tracker-button' => true } do
|
6
|
+
= fa_icon 'download'
|
7
|
+
= exporter.model_name.human
|
8
|
+
|
9
|
+
- else
|
3
10
|
.btn-group
|
4
|
-
%button.btn.btn-default
|
5
|
-
|
6
|
-
=
|
11
|
+
%button.btn.btn-default{ type: 'button', data: { toggle: 'dropdown' } }
|
12
|
+
= fa_icon 'download'
|
13
|
+
= t('para.export.name')
|
7
14
|
%span.caret
|
8
|
-
%ul.dropdown-menu
|
9
|
-
-
|
15
|
+
%ul.dropdown-menu
|
16
|
+
- component.exporters.each do |exporter|
|
10
17
|
%li
|
11
|
-
= link_to component.
|
12
|
-
=
|
18
|
+
= link_to component.path(namespace: :exports, exporter: exporter.name, q: params[:q]), method: :post, remote: true, data: { :'job-tracker-button' => true } do
|
19
|
+
= exporter.model_name.human
|
@@ -2,7 +2,7 @@
|
|
2
2
|
- if component.importers.length == 1
|
3
3
|
- importer = component.importers.first
|
4
4
|
|
5
|
-
= link_to component.path(namespace: :import, action: :new, importer: importer.model_name.singular_route_key), class: 'btn btn-default', remote: true, data: { :'
|
5
|
+
= link_to component.path(namespace: :import, action: :new, importer: importer.model_name.singular_route_key), class: 'btn btn-default', remote: true, data: { :'job-tracker-button' => true } do
|
6
6
|
= fa_icon 'upload'
|
7
7
|
= importer.model_name.human
|
8
8
|
|
@@ -15,5 +15,5 @@
|
|
15
15
|
%ul.dropdown-menu
|
16
16
|
- component.importers.each do |importer|
|
17
17
|
%li
|
18
|
-
= link_to component.path(:
|
18
|
+
= link_to component.path(namespace: :import, action: :new, importer: importer.model_name.singular_route_key), remote: true, data: { :'job-tracker-button' => true } do
|
19
19
|
= importer.model_name.human
|
data/config/locales/fr.yml
CHANGED
@@ -14,13 +14,21 @@ fr:
|
|
14
14
|
clone:
|
15
15
|
success: "%{model} cloné(e)"
|
16
16
|
error: "Impossible de cloner le(a) %{model}"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
|
18
|
+
jobs:
|
19
|
+
para/importer/base:
|
20
|
+
progressing: "Le fichier est en cours d'import, merci de patienter quelques instants ..."
|
21
|
+
success: "L'import du fichier a été effectué avec succès"
|
22
|
+
success_with_errors: |
|
23
|
+
L'import du fichier a été effectué, mais certaines lignes n'ont pas
|
24
|
+
été prises en compte à causes d'erreurs :
|
25
|
+
other_errors: "<br>Et <b>%{count}</b> autres erreurs ..."
|
26
|
+
error: "Le fichier choisi contient des erreurs et n'a pu être importé"
|
27
|
+
|
28
|
+
para/exporter/base:
|
29
|
+
progressing: "Le fichier est en cours d'export, merci de patienter quelques instants ..."
|
30
|
+
success: "Le fichier d'export a été généré, son téléchargement va démarrer ..."
|
31
|
+
error: "Erreur lors de la génération du fichier d'export ..."
|
24
32
|
|
25
33
|
admin:
|
26
34
|
title: "Administration"
|
@@ -57,7 +65,7 @@ fr:
|
|
57
65
|
empty: "Aucune entrée créée pour le moment. Créez une nouvelle entrée avec le bouton suivant :"
|
58
66
|
|
59
67
|
export:
|
60
|
-
name: "
|
68
|
+
name: "Exporter"
|
61
69
|
as: "Exporter (.%{extension})"
|
62
70
|
|
63
71
|
import:
|
@@ -69,7 +77,6 @@ fr:
|
|
69
77
|
cliquez sur le bouton "Importer".
|
70
78
|
placeholder: Fichier au format ( .csv .xlsx )
|
71
79
|
row_error_prefix: "Ligne %{number} :"
|
72
|
-
importing_file: "Le fichier est en cours d'import, merci de patienter quelques instants ..."
|
73
80
|
|
74
81
|
shared:
|
75
82
|
save: "Enregistrer"
|
@@ -116,7 +123,9 @@ fr:
|
|
116
123
|
settings_rails/form:
|
117
124
|
one: "Configuration"
|
118
125
|
other: "Configuration"
|
119
|
-
|
126
|
+
para/exporter/base:
|
127
|
+
one: "Export des données"
|
128
|
+
other: "Exports des données"
|
120
129
|
date:
|
121
130
|
formats:
|
122
131
|
admin: '%d/%m/%Y'
|
@@ -4,21 +4,19 @@ module Para
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
configurable_on :
|
8
|
-
end
|
7
|
+
configurable_on :exporters
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
define_method(:exporters) do
|
10
|
+
@exporters ||= if (exporters = configuration['exporters'].presence)
|
11
|
+
eval(exporters).map(&:constantize)
|
12
|
+
else
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
end
|
12
16
|
end
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
def exports
|
17
|
-
@exports ||= if export.present?
|
18
|
-
eval(export).map(&:with_indifferent_access)
|
19
|
-
else
|
20
|
-
[]
|
21
|
-
end
|
18
|
+
def exportable?
|
19
|
+
@exportable ||= exporters.length > 0
|
22
20
|
end
|
23
21
|
end
|
24
22
|
end
|
data/lib/para/exporter/base.rb
CHANGED
@@ -1,39 +1,41 @@
|
|
1
1
|
module Para
|
2
2
|
module Exporter
|
3
|
-
class Base
|
4
|
-
attr_reader :
|
5
|
-
class_attribute :model_name
|
3
|
+
class Base < Para::Job::Base
|
4
|
+
attr_reader :name, :model, :options
|
6
5
|
|
7
|
-
def
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
def model
|
12
|
-
@model ||= if (model_name = self.class.model_name)
|
13
|
-
model_name.constantize
|
14
|
-
else
|
15
|
-
raise 'You must define model to export in your exporter as following: `exports \'YourModelName\'`'
|
16
|
-
end
|
17
|
-
end
|
6
|
+
def perform(model_name: nil, **options)
|
7
|
+
@model = model_name && model_name.constantize
|
8
|
+
@options = options
|
9
|
+
@name = model.try(:model_name).try(:route_key).try(:parameterize)
|
18
10
|
|
19
|
-
|
20
|
-
|
11
|
+
# Render file and store it in a Library::File object, allowing us
|
12
|
+
# to retrieve that file easily from the job and subsequent requests
|
13
|
+
#
|
14
|
+
file = Para::Library::File.create!(attachment: render)
|
15
|
+
store(:file_gid, file.to_global_id)
|
21
16
|
end
|
22
17
|
|
23
|
-
def
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def extension
|
28
|
-
raise '#extension must be defined to create the export file name'
|
18
|
+
def file
|
19
|
+
@file ||= GlobalID::Locator.locate(store(:file_gid))
|
29
20
|
end
|
30
21
|
|
31
22
|
def file_name
|
32
|
-
@file_name ||= [name, extension].join
|
23
|
+
@file_name ||= [name, extension].join
|
33
24
|
end
|
34
25
|
|
35
|
-
|
36
|
-
|
26
|
+
private
|
27
|
+
|
28
|
+
# Allow passing a `:resources` option or a ransack search hash to filter
|
29
|
+
# exported resources
|
30
|
+
#
|
31
|
+
def resources
|
32
|
+
@resources ||= if options[:resources]
|
33
|
+
options[:resources]
|
34
|
+
elsif options[:search]
|
35
|
+
model.search(options[:search]).result
|
36
|
+
else
|
37
|
+
model.all
|
38
|
+
end
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
data/lib/para/exporter/csv.rb
CHANGED
@@ -3,32 +3,27 @@ require 'csv'
|
|
3
3
|
module Para
|
4
4
|
module Exporter
|
5
5
|
class Csv < Base
|
6
|
-
register_base_exporter :csv, self
|
7
|
-
|
8
|
-
def extension
|
9
|
-
'csv'
|
10
|
-
end
|
11
|
-
|
12
|
-
def mime_type
|
13
|
-
'text/csv'
|
14
|
-
end
|
15
|
-
|
16
|
-
def export_type
|
17
|
-
:excel
|
18
|
-
end
|
19
|
-
|
20
6
|
def render
|
21
|
-
CSV.generate do |csv|
|
7
|
+
data = CSV.generate do |csv|
|
22
8
|
csv << headers
|
23
9
|
|
24
10
|
resources.each do |resource|
|
25
11
|
csv << row_for(resource)
|
26
12
|
end
|
27
13
|
end
|
14
|
+
|
15
|
+
Tempfile.new([name, extension]).tap do |file|
|
16
|
+
file.write(data)
|
17
|
+
file.rewind
|
18
|
+
end
|
28
19
|
end
|
29
20
|
|
30
21
|
private
|
31
22
|
|
23
|
+
def extension
|
24
|
+
'.csv'
|
25
|
+
end
|
26
|
+
|
32
27
|
def headers
|
33
28
|
fields.map do |field|
|
34
29
|
encode(model.human_attribute_name(field))
|
data/lib/para/exporter.rb
CHANGED
@@ -1,55 +1,5 @@
|
|
1
1
|
module Para
|
2
2
|
module Exporter
|
3
|
-
class MissingExporterError < StandardError
|
4
|
-
attr_accessor :model_name, :format, :exporter_name
|
5
|
-
|
6
|
-
def initialize(model_name, format, exporter_name)
|
7
|
-
@model_name = model_name
|
8
|
-
@format = format
|
9
|
-
@exporter_name = exporter_name
|
10
|
-
end
|
11
|
-
|
12
|
-
def message
|
13
|
-
"No exporter found for model \"#{ model_name }\" and format " +
|
14
|
-
"\"#{ format }\". Please create the #{ exporter_name } class " +
|
15
|
-
"manually or with the following command : " +
|
16
|
-
"`rails g para:exporter #{ model_name.underscore } #{ format }"
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.for(model_name, format)
|
21
|
-
exporter_name = name_for(model_name, format)
|
22
|
-
|
23
|
-
begin
|
24
|
-
const_get(exporter_name)
|
25
|
-
rescue NameError => e
|
26
|
-
if e.message == "uninitialized constant Para::Exporter::#{ exporter_name }"
|
27
|
-
raise MissingExporterError.new(model_name, format, exporter_name)
|
28
|
-
else
|
29
|
-
raise e
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.name_for(model_name, format)
|
35
|
-
[
|
36
|
-
'',
|
37
|
-
format_exporter_name(format),
|
38
|
-
model_exporter_name(model_name)
|
39
|
-
].join('::')
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.model_exporter_name(model_name)
|
43
|
-
[model_name.to_s.pluralize, 'Exporter'].join
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.format_exporter_name(format)
|
47
|
-
format.to_s.camelize
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.base_exporters
|
51
|
-
@base_exporters ||= {}.with_indifferent_access
|
52
|
-
end
|
53
3
|
end
|
54
4
|
end
|
55
5
|
|
data/lib/para/importer/base.rb
CHANGED
@@ -1,12 +1,6 @@
|
|
1
1
|
module Para
|
2
2
|
module Importer
|
3
|
-
class Base <
|
4
|
-
include ActiveJob::Status
|
5
|
-
# Used to store import errors on the object
|
6
|
-
include ActiveModel::Validations
|
7
|
-
# Used to translate importer name with rails default `activemodel` i18n keys
|
8
|
-
extend ActiveModel::Naming
|
9
|
-
|
3
|
+
class Base < Para::Job::Base
|
10
4
|
rescue_from Exception, with: :rescue_exception
|
11
5
|
|
12
6
|
class_attribute :allows_import_errors
|
@@ -16,12 +10,11 @@ module Para
|
|
16
10
|
def perform(file, options = {})
|
17
11
|
@file = file
|
18
12
|
@sheet = Roo::Spreadsheet.open(file.attachment_path, options)
|
19
|
-
progress.total = sheet.last_row - 1
|
20
13
|
|
21
14
|
ActiveRecord::Base.transaction do
|
22
15
|
(2..(sheet.last_row)).each do |index|
|
23
16
|
begin
|
24
|
-
progress
|
17
|
+
progress!
|
25
18
|
import_from_row(sheet.row(index))
|
26
19
|
rescue ActiveRecord::RecordInvalid => error
|
27
20
|
if allows_import_errors?
|
@@ -33,11 +26,15 @@ module Para
|
|
33
26
|
end
|
34
27
|
end
|
35
28
|
|
36
|
-
|
29
|
+
save_errors!
|
37
30
|
end
|
38
31
|
|
39
32
|
private
|
40
33
|
|
34
|
+
def progress_total
|
35
|
+
sheet.last_row - 1
|
36
|
+
end
|
37
|
+
|
41
38
|
def import_from_row(row)
|
42
39
|
raise '#import_from_row(row) must be defined'
|
43
40
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Para
|
2
|
+
module Job
|
3
|
+
class Base < ActiveJob::Base
|
4
|
+
include ActiveJob::Status
|
5
|
+
# Used to store import errors on the object
|
6
|
+
include ActiveModel::Validations
|
7
|
+
# Used to translate importer name with rails default `activemodel` i18n keys
|
8
|
+
extend ActiveModel::Translation
|
9
|
+
|
10
|
+
before_perform :store_job_type
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def store_job_type
|
15
|
+
status.update(job_type: self.class.name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def progress!
|
19
|
+
ensure_total_progress
|
20
|
+
progress.increment
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_errors!
|
24
|
+
status.update(errors: errors.full_messages)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Default total progress to nil, making the UI show an animated porgress
|
28
|
+
# bar, indicating work is in progress, but not the exact progress
|
29
|
+
def total_progress
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def ensure_total_progress
|
34
|
+
return if @total_progress
|
35
|
+
|
36
|
+
@total_progress ||= if respond_to?(:progress_total)
|
37
|
+
progress.total = progress_total
|
38
|
+
else
|
39
|
+
progress[:total]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def store(key, value = nil)
|
44
|
+
if value
|
45
|
+
status.update(key => value)
|
46
|
+
else
|
47
|
+
status[key]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/para/job.rb
ADDED
data/lib/para/version.rb
CHANGED
data/lib/para.rb
CHANGED
data/lib/rails/routing_mapper.rb
CHANGED
@@ -47,6 +47,7 @@ module ActionDispatch
|
|
47
47
|
#
|
48
48
|
controller = options.fetch(:controller, '/para/admin/crud_resources')
|
49
49
|
imports_controller = options.fetch(:imports_controller, '/para/admin/imports')
|
50
|
+
exports_controller = options.fetch(:exports_controller, '/para/admin/exports')
|
50
51
|
|
51
52
|
namespace :admin do
|
52
53
|
constraints Para::Routing::ComponentControllerConstraint.new(controller) do
|
@@ -56,7 +57,6 @@ module ActionDispatch
|
|
56
57
|
collection do
|
57
58
|
patch :order
|
58
59
|
patch :tree
|
59
|
-
get :export
|
60
60
|
end
|
61
61
|
|
62
62
|
member do
|
@@ -70,6 +70,10 @@ module ActionDispatch
|
|
70
70
|
scope ':importer' do
|
71
71
|
resources :imports, controller: imports_controller
|
72
72
|
end
|
73
|
+
|
74
|
+
scope ':exporter' do
|
75
|
+
resources :exports, controller: exports_controller
|
76
|
+
end
|
73
77
|
end
|
74
78
|
end
|
75
79
|
end
|
data/lib/tasks/para_tasks.rake
CHANGED
@@ -9,9 +9,9 @@ namespace :para do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
Para::Component::Base.where(type: 'Para::Component::SingletonResource').pluck(:identifier, :id).each do |identifier, id|
|
12
|
-
Para::
|
13
|
-
component_id:
|
14
|
-
|
12
|
+
if (form_component_id = Para::Component::Form.find_by_identifier(identifier).try(:id))
|
13
|
+
Para::ComponentResource.where(component_id: id).update_all(component_id: form_component_id)
|
14
|
+
end
|
15
15
|
|
16
16
|
Para::Component::Base.where(id: id).destroy_all
|
17
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: para
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Valentin Ballestrino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -388,7 +388,7 @@ files:
|
|
388
388
|
- app/assets/javascripts/para/admin.coffee
|
389
389
|
- app/assets/javascripts/para/admin/async-progress.coffee
|
390
390
|
- app/assets/javascripts/para/admin/filters-form.coffee
|
391
|
-
- app/assets/javascripts/para/admin/
|
391
|
+
- app/assets/javascripts/para/admin/job-tracker.coffee
|
392
392
|
- app/assets/javascripts/para/admin/table.coffee
|
393
393
|
- app/assets/javascripts/para/admin/tabs.coffee
|
394
394
|
- app/assets/javascripts/para/admin/theme_actions.coffee
|
@@ -438,8 +438,10 @@ files:
|
|
438
438
|
- app/controllers/para/admin/base_controller.rb
|
439
439
|
- app/controllers/para/admin/component_controller.rb
|
440
440
|
- app/controllers/para/admin/crud_resources_controller.rb
|
441
|
+
- app/controllers/para/admin/exports_controller.rb
|
441
442
|
- app/controllers/para/admin/form_resources_controller.rb
|
442
443
|
- app/controllers/para/admin/imports_controller.rb
|
444
|
+
- app/controllers/para/admin/jobs_controller.rb
|
443
445
|
- app/controllers/para/admin/main_controller.rb
|
444
446
|
- app/controllers/para/admin/resources_controller.rb
|
445
447
|
- app/controllers/para/admin/settings_component_controller.rb
|
@@ -456,7 +458,6 @@ files:
|
|
456
458
|
- app/helpers/para/admin/page_helper.rb
|
457
459
|
- app/helpers/para/admin/resources_helper.rb
|
458
460
|
- app/helpers/para/application_helper.rb
|
459
|
-
- app/helpers/para/exports_helper.rb
|
460
461
|
- app/helpers/para/flash_helper.rb
|
461
462
|
- app/helpers/para/form_helper.rb
|
462
463
|
- app/helpers/para/markup_helper.rb
|
@@ -465,6 +466,7 @@ files:
|
|
465
466
|
- app/helpers/para/ordering_helper.rb
|
466
467
|
- app/helpers/para/search_helper.rb
|
467
468
|
- app/helpers/para/tag_helper.rb
|
469
|
+
- app/helpers/para/translations_helper.rb
|
468
470
|
- app/helpers/para/tree_helper.rb
|
469
471
|
- app/models/para/ability.rb
|
470
472
|
- app/models/para/component/base.rb
|
@@ -477,16 +479,17 @@ files:
|
|
477
479
|
- app/models/para/library.rb
|
478
480
|
- app/models/para/library/file.rb
|
479
481
|
- app/models/para/page/section.rb
|
482
|
+
- app/views/admin/para/exporter/bases/_completed.html.haml
|
480
483
|
- app/views/layouts/para/admin.html.haml
|
481
484
|
- app/views/layouts/para/application.html.erb
|
482
485
|
- app/views/para/admin/crud_resources/index.html.haml
|
483
486
|
- app/views/para/admin/dashboard.html.haml
|
484
487
|
- app/views/para/admin/form_resources/show.html.haml
|
485
|
-
- app/views/para/admin/imports/_completed.html.haml
|
486
|
-
- app/views/para/admin/imports/_failed.html.haml
|
487
|
-
- app/views/para/admin/imports/_progress.html.haml
|
488
488
|
- app/views/para/admin/imports/new.html.haml
|
489
|
-
- app/views/para/admin/
|
489
|
+
- app/views/para/admin/jobs/_completed.html.haml
|
490
|
+
- app/views/para/admin/jobs/_failed.html.haml
|
491
|
+
- app/views/para/admin/jobs/_progress.html.haml
|
492
|
+
- app/views/para/admin/jobs/show.html.haml
|
490
493
|
- app/views/para/admin/main/index.html.haml
|
491
494
|
- app/views/para/admin/resources/_actions.html.haml
|
492
495
|
- app/views/para/admin/resources/_add_button.html.haml
|
@@ -612,6 +615,8 @@ files:
|
|
612
615
|
- lib/para/inputs.rb
|
613
616
|
- lib/para/inputs/nested_many_input.rb
|
614
617
|
- lib/para/inputs/nested_one_input.rb
|
618
|
+
- lib/para/job.rb
|
619
|
+
- lib/para/job/base.rb
|
615
620
|
- lib/para/logging.rb
|
616
621
|
- lib/para/logging/active_job_log_subscriber.rb
|
617
622
|
- lib/para/markup.rb
|
@@ -668,7 +673,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
668
673
|
version: '0'
|
669
674
|
requirements: []
|
670
675
|
rubyforge_project:
|
671
|
-
rubygems_version: 2.
|
676
|
+
rubygems_version: 2.6.7
|
672
677
|
signing_key:
|
673
678
|
specification_version: 4
|
674
679
|
summary: Rails admin engine
|
@@ -1,10 +0,0 @@
|
|
1
|
-
= modal id: "component-import-#{ @importer.model_name.route_key }", data: { :'import-status-url' => @component.path(namespace: :import, importer: @importer.name, id: @status.job_id) } do |modal|
|
2
|
-
= modal.header do
|
3
|
-
= @importer.model_name.human
|
4
|
-
|
5
|
-
- if @status.failed?
|
6
|
-
= render partial: 'failed', locals: { modal: modal }
|
7
|
-
- elsif @status.completed?
|
8
|
-
= render partial: 'completed', locals: { modal: modal, status: @status }
|
9
|
-
- else
|
10
|
-
= render partial: 'progress', locals: { modal: modal, status: @status }
|