activeadmin-async_exporter 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8e9c289aefbcaad8af51ff0246db57904394a35f
4
+ data.tar.gz: 447a795995d280e8108aa4a59fd40ed4328f5a4b
5
+ SHA512:
6
+ metadata.gz: 873deb4aad5fea35066b3611980ef38ad9aaa6618521b8a3084772a662a276792857689bdd6ec3654a08f69e713d45df4ae383e4f19b4bd2fbdec5d72178f030
7
+ data.tar.gz: '09b210f28ea23c0e74ed46cf35734091489a6fb645d4958eca1e936159ff5d676dc10934763178631e37760cc2104a054bdca2751ec32521c274d78e1e952917'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_admin'
4
+
5
+ require 'active_admin/async_exporter/version'
6
+ require 'active_admin/async_exporter/config'
7
+ require 'active_admin/async_exporter/reports/dsl'
8
+ require 'active_admin/async_exporter/reports/worker'
9
+ require 'active_admin/async_exporter/services/storage_service'
10
+ require 'active_admin/async_exporter/services/disk_service'
11
+ require 'active_admin/async_exporter/services/aws_s3_service'
12
+
13
+ module ActiveAdmin
14
+ module AsyncExporter
15
+ class Railtie < ::Rails::Railtie
16
+ config.after_initialize do
17
+ ActiveAdmin::ResourceDSL.include ActiveAdmin::AsyncExporter::Reports::DSL
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ class << self
6
+ def configure
7
+ yield config
8
+ end
9
+
10
+ def config
11
+ @config ||= Config.new
12
+ end
13
+ end
14
+
15
+ class Config
16
+ attr_accessor :service, :aws_bucket_name, :aws_bucket_folder_path, :disk_folder_path
17
+
18
+ def initialize
19
+ @aws_bucket_name = nil
20
+ @aws_bucket_folder_path = nil
21
+ @disk_folder_path = nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ module Reports
6
+ module DSL
7
+ attr_reader :csv_fields
8
+
9
+ def csv_async(decorate_model: false, file_name: nil)
10
+ @csv_fields ||= {}
11
+
12
+ yield
13
+
14
+ csv_report(columns: csv_fields, decorate_model: decorate_model, file_name: file_name)
15
+ end
16
+
17
+ def csv_column(column_name, column_value = nil, &block)
18
+ column_value ||= column_name
19
+
20
+ column_value = block.source if block_given?
21
+
22
+ raise ActiveAdmin::AsyncExporter::Error, 'No argument given' if block_given? &&
23
+ block.parameters.blank?
24
+
25
+ csv_fields[column_name.to_sym] = { block: block_given?, value: column_value.to_s }
26
+ end
27
+
28
+ def csv_report(columns:, decorate_model: false, file_name: nil)
29
+ action_item :download_csv, only: :index do
30
+ link_to 'Download CSV',
31
+ { action: :download_csv, params: params.to_enum.to_h },
32
+ method: :post,
33
+ data: { confirm: 'Are you sure you want to generate this report?' }
34
+ end
35
+
36
+ collection_action :download_csv, method: :post do
37
+ admin_report = AdminReport.create!(
38
+ author_id: current_admin_user.id,
39
+ entity: current_collection.name,
40
+ status: :pending
41
+ )
42
+
43
+ options = {
44
+ admin_report_id: admin_report.id,
45
+ controller: self.class.name,
46
+ columns: columns,
47
+ decorate_model: decorate_model,
48
+ file_name: file_name,
49
+ query: params['q']
50
+ }
51
+
52
+ ActiveAdmin::AsyncExporter::Worker.perform_later(options)
53
+ redirect_to admin_admin_report_path(admin_report)
54
+ end
55
+
56
+ controller do
57
+ def current_collection
58
+ scoped_collection
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ class Error < ActiveAdmin::Error; end
66
+ end
67
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ class Worker < ApplicationJob
6
+ def perform(options = {})
7
+ controller = options[:controller].classify.constantize.new
8
+ columns = options[:columns]
9
+ file_name = filename(options[:file_name], controller)
10
+
11
+ path = Rails.root.join('tmp', file_name)
12
+
13
+ CSV.open(path, 'wb', headers: true) do |csv|
14
+ build_csv(csv, columns, controller, options)
15
+ end
16
+
17
+ file = Services::StorageService.call(path: path, name: file_name).store
18
+ AdminReport.find(options[:admin_report_id]).update_attributes!(
19
+ status: :ready,
20
+ location_url: file.url
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def proc_from_string(code_string)
27
+ proc_string = code_from_string(code_string)
28
+ Proc.class_eval("proc #{proc_string}")
29
+ end
30
+
31
+ def code_from_string(code_string)
32
+ code_string.scan(%r{/(do|{)\s+(\|.*\|)(.*)(end|})/m}).flatten.join('')
33
+ end
34
+
35
+ def collection(controller, options)
36
+ controller.current_collection.ransack(options[:query]).result
37
+ end
38
+
39
+ def filename(file_name, controller)
40
+ file_name ||= controller.current_collection.name.pluralize.downcase
41
+ [file_name, Time.current.to_i].join('_') + '.csv'
42
+ end
43
+
44
+ def build_csv(csv, columns, controller, options)
45
+ headers = columns.keys
46
+ evaluators = columns.values
47
+
48
+ csv << headers.map(&:to_s)
49
+
50
+ collection(controller, options).find_in_batches do |group|
51
+ group.each do |model|
52
+ model = model.decorate if options[:decorate_model]
53
+
54
+ csv << csv_field_value(model, evaluators)
55
+ end
56
+ end
57
+ end
58
+
59
+ def csv_field_value(model, evaluators)
60
+ evaluators.collect do |ev|
61
+ value = ev[:value]
62
+
63
+ if ev[:block]
64
+ code_proc = proc_from_string(value)
65
+ code_proc.yield(model)
66
+ else
67
+ model.send(value)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ module Services
6
+ class AwsS3Service
7
+ attr_accessor :file, :s3, :bucket, :object
8
+
9
+ def initialize(file)
10
+ @file = file
11
+ @s3 = Aws::S3::Resource.new
12
+ @bucket = s3.bucket(ActiveAdmin::AsyncExporter.config.aws_bucket_name)
13
+ end
14
+
15
+ def store
16
+ @object = bucket.object(filename)
17
+ object.upload_file(Pathname.new(file[:path]), { acl: 'public-read' })
18
+ self
19
+ end
20
+
21
+ def url
22
+ object.public_url.to_s
23
+ end
24
+
25
+ def delete
26
+ bucket.delete_objects({ delete: { objects: [{ key: filename }] } })
27
+ end
28
+
29
+ private
30
+
31
+ def filename
32
+ @filename ||= "#{bucket_folder}#{file[:name]}"
33
+ end
34
+
35
+ def bucket_folder
36
+ path = ActiveAdmin::AsyncExporter.config.aws_bucket_folder_path
37
+ return '' if path.blank?
38
+
39
+ "#{path}/"
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ module Services
6
+ class DiskService
7
+ attr_accessor :file, :folder
8
+
9
+ def initialize(file)
10
+ @file = file
11
+ @folder = ActiveAdmin::AsyncExporter.config.disk_folder_path
12
+ end
13
+
14
+ def store
15
+ make_dir
16
+ @object = IO.copy_stream(file[:path], filename)
17
+ self
18
+ end
19
+
20
+ def url
21
+ filename
22
+ end
23
+
24
+ def delete
25
+ File.delete(filename)
26
+ end
27
+
28
+ private
29
+
30
+ def make_dir
31
+ FileUtils.mkdir_p(folder)
32
+ end
33
+
34
+ def filename
35
+ @filename ||= "#{folder}/#{file[:name]}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ module Services
6
+ class StorageService
7
+ private_class_method :new
8
+
9
+ def self.call(*args)
10
+ case ActiveAdmin::AsyncExporter.config.service
11
+ when :amazon
12
+ Services::AwsS3Service.new(*args)
13
+ else
14
+ Services::DiskService.new(*args)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module AsyncExporter
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_admin/async_exporter'
@@ -0,0 +1,13 @@
1
+ module ActiveAdmin
2
+ module AsyncExporter
3
+ module Generators
4
+ class ConfigGenerator < Rails::Generators::Base
5
+ source_root File.join(__dir__, 'templates')
6
+
7
+ def copy_config_file
8
+ template 'async_exporter_config.rb', 'config/initializers/activeadmin-async_exporter.rb'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveAdmin::AsyncExporter.configure do |config|
4
+ ## Storage Service
5
+ # The available options are :local, :amazon
6
+ # default option: :local
7
+ # config.service = :local
8
+
9
+ ## AWS S3
10
+ # config.aws_bucket_name = nil
11
+ # config.aws_bucket_folder_path = nil
12
+
13
+ ## Disk
14
+ config.disk_folder_path = Rails.root.join('exporter')
15
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/active_record'
4
+
5
+ module ActiveAdmin
6
+ module AsyncExporter
7
+ module Generators
8
+ class InstallGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ argument :user_class, type: :string, default: 'User'
12
+
13
+ source_root File.join(__dir__, 'templates')
14
+
15
+ def configure
16
+ create_admin_reports_migration
17
+ create_admin_reports_model
18
+ create_active_admin_view
19
+ end
20
+
21
+ private
22
+
23
+ def create_admin_reports_migration
24
+ migration_template(
25
+ 'migration.rb',
26
+ 'db/migrate/add_admin_reports.rb',
27
+ user_table_name: user_table_name
28
+ )
29
+ end
30
+
31
+ def create_admin_reports_model
32
+ template(
33
+ 'admin_report.rb',
34
+ 'app/models/admin_report.rb',
35
+ user_class: user_class
36
+ )
37
+ end
38
+
39
+ def create_active_admin_view
40
+ template(
41
+ 'admin_reports.rb',
42
+ 'app/admin/admin_reports.rb'
43
+ )
44
+ end
45
+
46
+ def user_class_name
47
+ user_class.underscore.singularize
48
+ end
49
+
50
+ def user_table_name
51
+ user_class_name.pluralize
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AdminReport < ApplicationRecord
4
+ enum status: { pending: 0, ready: 1, error: 2 }
5
+
6
+ belongs_to :author, class_name: '<%= user_class %>'
7
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'open-uri'
5
+
6
+ ActiveAdmin.register AdminReport do
7
+ actions :index, :show, :destroy
8
+
9
+ filter :entity
10
+ filter :format
11
+ filter :created_at
12
+ filter :location_url
13
+
14
+ index do
15
+ id_column
16
+ column :author
17
+ column :entity
18
+ column :format
19
+ column :status
20
+ column :file do |report|
21
+ link_to 'Download CSV', download_file_admin_admin_report_path(report) if report.ready?
22
+ end
23
+ column :created_at
24
+
25
+ actions
26
+ end
27
+
28
+ show do
29
+ attributes_table_for(admin_report) do
30
+ row :id
31
+ row :author
32
+ row :entity
33
+ row :format
34
+ row 'Status' do
35
+ status_tag (admin_report.ready? ? 'green' : 'orange'), label: admin_report.status
36
+ end
37
+ row :file do
38
+ link_to 'Download CSV', download_file_admin_admin_report_path if admin_report.ready?
39
+ end
40
+ row :created_at
41
+ end
42
+ end
43
+
44
+ member_action :download_file, method: :get do
45
+ send_data(open(resource.location_url).read, disposition: 'attachment', filename: /(?!.*\/).+/.match(resource.location_url)[0])
46
+ end
47
+
48
+ controller do
49
+ def scoped_collection
50
+ AdminReport.includes(:author)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
+ def change
3
+ create_table :admin_reports do |t|
4
+ t.references :author
5
+ t.foreign_key :<%= user_table_name %>, column: :author_id, primary_key: 'id'
6
+
7
+ t.string :entity, null: false
8
+ t.string :format, null: false, default: :csv
9
+ t.integer :status, null: false
10
+ t.string :location_url
11
+
12
+ t.timestamps
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activeadmin-async_exporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Franco Pariani
8
+ - Horacio Bertorello
9
+ - Ricardo Cortio
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2019-10-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activeadmin
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.pre1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 1.0.0.pre1
29
+ - !ruby/object:Gem::Dependency
30
+ name: aws-sdk-s3
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1'
43
+ - !ruby/object:Gem::Dependency
44
+ name: rails
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.0.0
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.0.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: rspec
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 3.9.0
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 3.9.0
71
+ - !ruby/object:Gem::Dependency
72
+ name: rubocop-rails
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: '2.3'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '2.3'
85
+ - !ruby/object:Gem::Dependency
86
+ name: rubocop-rootstrap
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 0.1.0
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: 0.1.0
99
+ - !ruby/object:Gem::Dependency
100
+ name: simplecov
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 0.17.1
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: 0.17.1
113
+ description: Async exporter for Active Admin using ActiveJob
114
+ email: franco@rootstrap.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - lib/active_admin/async_exporter.rb
120
+ - lib/active_admin/async_exporter/config.rb
121
+ - lib/active_admin/async_exporter/reports/dsl.rb
122
+ - lib/active_admin/async_exporter/reports/worker.rb
123
+ - lib/active_admin/async_exporter/services/aws_s3_service.rb
124
+ - lib/active_admin/async_exporter/services/disk_service.rb
125
+ - lib/active_admin/async_exporter/services/storage_service.rb
126
+ - lib/active_admin/async_exporter/version.rb
127
+ - lib/activeadmin-async_exporter.rb
128
+ - lib/generators/active_admin/async_exporter/config/config_generator.rb
129
+ - lib/generators/active_admin/async_exporter/config/templates/async_exporter_config.rb.tt
130
+ - lib/generators/active_admin/async_exporter/install/install_generator.rb
131
+ - lib/generators/active_admin/async_exporter/install/templates/admin_report.rb.tt
132
+ - lib/generators/active_admin/async_exporter/install/templates/admin_reports.rb.tt
133
+ - lib/generators/active_admin/async_exporter/install/templates/migration.rb.tt
134
+ homepage: https://rubygems.org/gems/activeadmin-async_exporter
135
+ licenses:
136
+ - MIT
137
+ metadata:
138
+ source_code_uri: https://github.com/rootstrap/activeadmin-async_exporter
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.5.2.3
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Async exporter for Active Admin using ActiveJob
159
+ test_files: []