activeadmin-async_exporter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []