rails_execution 0.1.1

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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +129 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +35 -0
  7. data/Rakefile +3 -0
  8. data/app/assets/images/executions/approved.png +0 -0
  9. data/app/assets/images/executions/favicon.png +0 -0
  10. data/app/assets/images/executions/logo.png +0 -0
  11. data/app/assets/images/executions/rejected.png +0 -0
  12. data/app/assets/images/executions/robot.png +0 -0
  13. data/app/assets/javascripts/executions/ace.min.js +1 -0
  14. data/app/assets/javascripts/executions/ace.modes.ruby.min.js +1 -0
  15. data/app/assets/javascripts/executions/ace.theme-solarized_dark.min.js +1 -0
  16. data/app/assets/javascripts/executions/base.js +1 -0
  17. data/app/assets/javascripts/executions/bootstrap.5.2.1.min.js +6 -0
  18. data/app/assets/javascripts/executions/chart.min.js +13 -0
  19. data/app/assets/javascripts/executions/comments.js +43 -0
  20. data/app/assets/javascripts/executions/easymde.min.js +7 -0
  21. data/app/assets/javascripts/executions/events.js +8 -0
  22. data/app/assets/javascripts/executions/highlight.min.js +1198 -0
  23. data/app/assets/javascripts/executions/highlight.ruby.min.js +51 -0
  24. data/app/assets/javascripts/executions/jquery-3.6.1.min.js +2 -0
  25. data/app/assets/javascripts/executions/marked.min.js +6 -0
  26. data/app/assets/javascripts/executions/rails.js +565 -0
  27. data/app/assets/javascripts/executions/select2.min.js +2 -0
  28. data/app/assets/stylesheets/executions/base.css +3 -0
  29. data/app/assets/stylesheets/executions/bootstrap-icons.css +1869 -0
  30. data/app/assets/stylesheets/executions/comments.css +14 -0
  31. data/app/assets/stylesheets/executions/easymde.min.css +7 -0
  32. data/app/assets/stylesheets/executions/fonts.css +4 -0
  33. data/app/assets/stylesheets/executions/highlight.min.css +9 -0
  34. data/app/assets/stylesheets/executions/modify.scss +278 -0
  35. data/app/assets/stylesheets/executions/select2.min.css +1 -0
  36. data/app/controllers/rails_execution/base_controller.rb +16 -0
  37. data/app/controllers/rails_execution/comments_controller.rb +34 -0
  38. data/app/controllers/rails_execution/dashboards_controller.rb +27 -0
  39. data/app/controllers/rails_execution/tasks_controller.rb +175 -0
  40. data/app/helpers/rails_execution/base_helper.rb +14 -0
  41. data/app/helpers/rails_execution/policy_helper.rb +64 -0
  42. data/app/helpers/rails_execution/rendering_helper.rb +94 -0
  43. data/app/models/rails_execution/activity.rb +8 -0
  44. data/app/models/rails_execution/comment.rb +8 -0
  45. data/app/models/rails_execution/task.rb +71 -0
  46. data/app/models/rails_execution/task_review.rb +14 -0
  47. data/app/views/layouts/execution.html.haml +16 -0
  48. data/app/views/rails_execution/comments/_comment.html.haml +23 -0
  49. data/app/views/rails_execution/comments/create.js.haml +6 -0
  50. data/app/views/rails_execution/comments/update.js.haml +2 -0
  51. data/app/views/rails_execution/dashboards/charts/_insights.html.haml +27 -0
  52. data/app/views/rails_execution/dashboards/home.html.haml +24 -0
  53. data/app/views/rails_execution/dashboards/insights.json.jbuilder +8 -0
  54. data/app/views/rails_execution/shared/_flash.html.haml +7 -0
  55. data/app/views/rails_execution/shared/_header.html.haml +13 -0
  56. data/app/views/rails_execution/shared/_paging.html.haml +35 -0
  57. data/app/views/rails_execution/tasks/_actions.html.haml +12 -0
  58. data/app/views/rails_execution/tasks/_activities.html.haml +22 -0
  59. data/app/views/rails_execution/tasks/_attachment_file_fields.html.haml +3 -0
  60. data/app/views/rails_execution/tasks/_attachment_files.html.haml +12 -0
  61. data/app/views/rails_execution/tasks/_comments.html.haml +15 -0
  62. data/app/views/rails_execution/tasks/_form.html.haml +55 -0
  63. data/app/views/rails_execution/tasks/_form_scripts.html.haml +34 -0
  64. data/app/views/rails_execution/tasks/_new_comment.html.haml +8 -0
  65. data/app/views/rails_execution/tasks/_reviewers.html.haml +23 -0
  66. data/app/views/rails_execution/tasks/_script_content.html.haml +15 -0
  67. data/app/views/rails_execution/tasks/_show_scripts.html.haml +27 -0
  68. data/app/views/rails_execution/tasks/_status.html.haml +19 -0
  69. data/app/views/rails_execution/tasks/_task.html.haml +18 -0
  70. data/app/views/rails_execution/tasks/_tips.html.haml +18 -0
  71. data/app/views/rails_execution/tasks/closed.html.haml +12 -0
  72. data/app/views/rails_execution/tasks/completed.html.haml +12 -0
  73. data/app/views/rails_execution/tasks/edit.html.haml +3 -0
  74. data/app/views/rails_execution/tasks/index.html.haml +12 -0
  75. data/app/views/rails_execution/tasks/new.html.haml +3 -0
  76. data/app/views/rails_execution/tasks/show.html.haml +38 -0
  77. data/config/routes.rb +26 -0
  78. data/lib/generators/rails_execution/file_upload_generator.rb +18 -0
  79. data/lib/generators/rails_execution/install_generator.rb +36 -0
  80. data/lib/generators/rails_execution/templates/config.rb.tt +63 -0
  81. data/lib/generators/rails_execution/templates/file_reader.rb.tt +14 -0
  82. data/lib/generators/rails_execution/templates/file_uploader.rb.tt +11 -0
  83. data/lib/generators/rails_execution/templates/install.rb.tt +48 -0
  84. data/lib/rails_execution/app_model.rb +14 -0
  85. data/lib/rails_execution/config.rb +73 -0
  86. data/lib/rails_execution/engine.rb +24 -0
  87. data/lib/rails_execution/error.rb +6 -0
  88. data/lib/rails_execution/files/reader.rb +49 -0
  89. data/lib/rails_execution/files/uploader.rb +42 -0
  90. data/lib/rails_execution/services/approvement.rb +41 -0
  91. data/lib/rails_execution/services/execution.rb +87 -0
  92. data/lib/rails_execution/services/executor.rb +24 -0
  93. data/lib/rails_execution/services/paging.rb +35 -0
  94. data/lib/rails_execution/services/syntax_checker.rb +38 -0
  95. data/lib/rails_execution/version.rb +5 -0
  96. data/lib/rails_execution.rb +24 -0
  97. metadata +157 -0
@@ -0,0 +1,18 @@
1
+ %h5.section-title.text-muted
2
+ %i.bi.bi-lightbulb
3
+ %strong ProTip:
4
+ %span#all-tips
5
+ %span.tip
6
+ Use
7
+ %code Rails.logger.info 'message...'
8
+ to add a message into log file
9
+ - if RailsExecution.configuration.file_upload
10
+ %span.tip
11
+ Call
12
+ %code file('name')
13
+ to read the file IO
14
+ :javascript
15
+ var tipsCount = $('#all-tips span.tip').length;
16
+ var displayIndex = Math.floor(Math.random() * tipsCount);
17
+ $($('#all-tips span.tip')[displayIndex]).show();
18
+
@@ -0,0 +1,12 @@
1
+ = re_card_content do
2
+ %h5.section-title Closed
3
+ %ul.list-unstyled
4
+ - if @tasks.empty?
5
+ %li
6
+ %p.mt-4.fs-5.text-center.text-muted
7
+ %i.bi.bi-journal-code.me-2
8
+ Welcome to add new Task
9
+ - else
10
+ = render partial: 'task', collection: @tasks
11
+
12
+ = re_render_paging(@tasks)
@@ -0,0 +1,12 @@
1
+ = re_card_content do
2
+ %h5.section-title Completed
3
+ %ul.list-unstyled
4
+ - if @tasks.empty?
5
+ %li
6
+ %p.mt-4.fs-5.text-center.text-muted
7
+ %i.bi.bi-journal-code.me-2
8
+ Welcome to add new Task
9
+ - else
10
+ = render partial: 'task', collection: @tasks
11
+
12
+ = re_render_paging(@tasks)
@@ -0,0 +1,3 @@
1
+ = re_card_content do
2
+ %h6.border-bottom.pb-2.mb-0 Edit request
3
+ = render partial: 'form', locals: { task: @task }
@@ -0,0 +1,12 @@
1
+ = re_card_content do
2
+ %h5.section-title In processing
3
+ %ul.list-unstyled
4
+ - if @tasks.empty?
5
+ %li
6
+ %p.mt-4.fs-5.text-center.text-muted
7
+ %i.bi.bi-journal-code.me-2
8
+ Welcome to add new Task
9
+ - else
10
+ = render partial: 'task', collection: @tasks
11
+
12
+ = re_render_paging(@tasks)
@@ -0,0 +1,3 @@
1
+ = re_card_content do
2
+ %h6.border-bottom.pb-2.mb-0 New request
3
+ = render partial: 'form', locals: { task: @task }
@@ -0,0 +1,38 @@
1
+ = re_page_actions do
2
+ - if can_close_task?(current_task)
3
+ = link_to 'Close', task_path(current_task), class: 'btn btn-outline-danger', method: :delete, data: { confirm: 'Are you sure?' }
4
+ - elsif current_task.is_closed? && can_create_task?
5
+ = link_to 'Re-Open', reopen_task_path(current_task), class: 'btn btn-outline-info', method: :patch, data: { confirm: 'Are you sure?' }
6
+
7
+ - if can_edit_task?(current_task)
8
+ = link_to 'Edit', edit_task_path(current_task), class: 'btn btn-outline-warning me-2'
9
+
10
+ .row
11
+ .col-md-8
12
+ = re_card_content do
13
+ %h6.border-bottom.pb-2.mb-0
14
+ = current_task.title
15
+ %span.float-end.fs-5.text-muted ##{current_task.id}
16
+ %p.mt-3.description
17
+ - if current_task.description.present?
18
+ #description-markdown= current_task.description
19
+ #description-html.normal-text
20
+ - else
21
+ %p.text-muted.text-center No description
22
+
23
+ = render partial: 'attachment_files'
24
+ = render partial: 'script_content'
25
+ = render partial: 'comments', locals: { new_comment: @new_comment }
26
+
27
+ .col-md-4
28
+ - if display_owner?(current_task.owner)
29
+ = re_card_content do
30
+ %h5.section-title.mb-3 Requestor
31
+ = render_user_info(current_task.owner, avatar_size: '40x40')
32
+
33
+ = render partial: 'reviewers'
34
+ = render partial: 'status'
35
+ = render partial: 'actions'
36
+ = render partial: 'activities'
37
+
38
+ = render partial: 'show_scripts'
data/config/routes.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsExecution::Engine.routes.draw do
4
+ root to: 'dashboards#home'
5
+
6
+ resource :dashboards, only: [] do
7
+ get :insights
8
+ end
9
+
10
+ resources :tasks do
11
+ member do
12
+ patch :reopen
13
+ patch :reject
14
+ patch :approve
15
+ patch :execute
16
+ end
17
+
18
+ collection do
19
+ get :completed
20
+ get :closed
21
+ end
22
+
23
+ resources :comments, only: [:create, :update, :destroy]
24
+ end
25
+
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module RailsExecution
6
+ module Generators
7
+ class FileUploadGenerator < Rails::Generators::Base
8
+
9
+ source_root File.join(__dir__, 'templates')
10
+
11
+ def copy_config
12
+ template 'file_uploader.rb', 'lib/rails_execution/file_uploader.rb'
13
+ template 'file_reader.rb', 'lib/rails_execution/file_reader.rb'
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/migration'
5
+ require 'rails/generators/active_record'
6
+ require 'active_record'
7
+
8
+ module RailsExecution
9
+ module Generators
10
+ class InstallGenerator < Rails::Generators::Base
11
+
12
+ include ActiveRecord::Generators::Migration
13
+
14
+ source_root File.join(__dir__, 'templates')
15
+
16
+ def copy_migration
17
+ migration_template 'install.rb', 'db/migrate/install_execution.rb', migration_version: migration_version
18
+ end
19
+
20
+ def copy_config
21
+ template 'config.rb', 'config/initializers/rails_execution.rb'
22
+ end
23
+
24
+ def mount_engine
25
+ route "mount RailsExecution::Engine, at: 'execution'"
26
+ end
27
+
28
+ private
29
+
30
+ def migration_version
31
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ RailsExecution.configuration do |config|
2
+
3
+ config.solo_mode = true # Without reviewing process
4
+
5
+ # Owner display
6
+ # config.owner_model = 'User'
7
+ # config.owner_method = :current_user
8
+ # config.owner_name_method = :fullname
9
+ # config.owner_avatar = lambda do |owner|
10
+ # owner.avatar.url(:medium)
11
+ # end
12
+
13
+ # Task control
14
+ # config.reviewers = lambda do
15
+ # [{ name: '', id: '' }]
16
+ # end
17
+
18
+ # Accessible check: Default is TRUE
19
+ # config.task_editable
20
+ # config.task_closable
21
+ # config.task_creatable
22
+ # config.task_approvable
23
+ # config.task_executable = lambda do |task, user|
24
+ # YourPolicy.new(user, task).executable?
25
+ # end
26
+
27
+ # Advanced
28
+ # config.file_upload = true
29
+ # config.file_uploader = ::RailsExecution::FileUploader
30
+ # config.file_reader = ::RailsExecution::FileReader
31
+ # Defaults of acceptable_file_types: .png, .gif, .jpg, .jpeg, .pdf, .csv
32
+ # config.acceptable_file_types = {
33
+ # '.jpeg': 'image/jpeg',
34
+ # '.pdf': 'application/pdf',
35
+ # '.csv': ['text/csv', 'text/plain'],
36
+ # }
37
+
38
+ # Logger
39
+ # Using Paperclip
40
+ # config.logging = lambda do |log_file, task|
41
+ # YourAttachment.create(file: log_file, task: task)
42
+ # end
43
+ # config.logging_files = lambda do |task|
44
+ # YourAttachment.where(task: task).map { |item| item.file.url }
45
+ # end
46
+ # Using ActiveStorage
47
+ # config.logging = lambda do |log_file, task|
48
+ # attachment = YourAttachment.create!(task: task)
49
+ # attachment.file.attach({
50
+ # io: log_file,
51
+ # filename: Time.current.strftime('%Y%m%d_%H%M%S.log'),
52
+ # content_type: 'text/plain',
53
+ # })
54
+ # end
55
+ # config.logging_files = lambda do |task|
56
+ # ActiveStorage::Current.host = 'localhost:3000'
57
+ # YourAttachment.where(task: task).map { |item| item.file.url }
58
+ # end
59
+
60
+ # Paging
61
+ # config.per_page = 30 # Default: 20
62
+
63
+ end
@@ -0,0 +1,14 @@
1
+ module RailsExecution
2
+ class FileReader < RailsExecution::Files::Reader
3
+
4
+ # Output: {
5
+ # 'File1' => 'url',
6
+ # 'FileZ' => 'url',
7
+ # }
8
+ def call
9
+ # YourAttachment.where(task: task).pluck(:name, :url).to_h
10
+ return {}
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module RailsExecution
2
+ class FileUploader < RailsExecution::Files::Uploader
3
+
4
+ def call
5
+ files.each do |item|
6
+ # YourAttachment.create!(task: task, name: item.name, file: item.file)
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,48 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+
3
+ def change
4
+ create_table :rails_execution_tasks, force: true do |t|
5
+ t.timestamps null: false
6
+ t.column :title, :string, null: false
7
+ t.column :status, :string, null: false
8
+ t.column :syntax_status, :string, default: 'bad'
9
+ t.column :description, :text
10
+ t.column :script, :text
11
+ t.column :owner_id, :integer
12
+ t.column :owner_type, :string
13
+ end
14
+ add_index :rails_execution_tasks, :status
15
+ add_index :rails_execution_tasks, [:owner_id, :owner_type], name: :owner_index
16
+
17
+ create_table :rails_execution_comments, force: true do |t|
18
+ t.timestamps null: false
19
+ t.column :task_id, :integer, null: false
20
+ t.column :content, :text, null: false
21
+ t.column :owner_id, :integer
22
+ t.column :owner_type, :string
23
+ end
24
+ add_index :rails_execution_comments, :task_id
25
+ add_index :rails_execution_comments, [:owner_id, :owner_type], name: :owner_index
26
+
27
+ create_table :rails_execution_activities, force: true do |t|
28
+ t.timestamps null: false
29
+ t.column :task_id, :integer, null: false
30
+ t.column :message, :text, null: false
31
+ t.column :owner_id, :integer
32
+ t.column :owner_type, :string
33
+ end
34
+ add_index :rails_execution_activities, :task_id
35
+ add_index :rails_execution_activities, [:owner_id, :owner_type], name: :owner_index
36
+
37
+ create_table :rails_execution_task_reviews, force: true do |t|
38
+ t.timestamps null: false
39
+ t.column :task_id, :integer, null: false
40
+ t.column :status, :text, null: false
41
+ t.column :owner_id, :integer
42
+ t.column :owner_type, :string
43
+ end
44
+ add_index :rails_execution_task_reviews, :task_id
45
+ add_index :rails_execution_task_reviews, [:owner_id, :owner_type], name: :owner_index
46
+ end
47
+
48
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ model_klass = defined?(ApplicationRecord) ? ApplicationRecord : ActiveRecord::Base
4
+
5
+ class RailsExecution::AppModel < model_klass
6
+
7
+ self.abstract_class = true
8
+
9
+ belongs_to :owner, polymorphic: true
10
+
11
+ scope :descending, -> { order(id: :desc) }
12
+ scope :ascending, -> { order(id: :asc) }
13
+
14
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsExecution
4
+ class Config
5
+
6
+ DEFAULT_PER_PAGE = 20
7
+ DEFAULT_FILE_TYPES = {
8
+ '.png': 'image/png',
9
+ '.gif': 'image/gif',
10
+ '.jpg': 'image/jpg',
11
+ '.jpeg': 'image/jpeg',
12
+ '.pdf': 'application/pdf',
13
+ '.csv': ['text/csv', 'text/plain'],
14
+ }
15
+
16
+ attr_accessor :solo_mode # Solo mode without reviewing process
17
+
18
+ # Owner display
19
+ attr_accessor :owner_model
20
+ attr_accessor :owner_method
21
+ attr_accessor :owner_name_method
22
+ attr_accessor :owner_avatar # lambda
23
+
24
+ # Task control
25
+ attr_accessor :reviewers # lambda
26
+
27
+ # Accessible check
28
+ attr_accessor :task_editable # lambda
29
+ attr_accessor :task_closable # lambda
30
+ attr_accessor :task_creatable # lambda
31
+ attr_accessor :task_approvable # lambda
32
+ attr_accessor :task_executable # lambda
33
+
34
+ # Advanced
35
+ attr_accessor :file_upload
36
+ attr_accessor :file_uploader
37
+ attr_accessor :file_reader
38
+ attr_accessor :acceptable_file_types
39
+
40
+ # Logger
41
+ attr_accessor :logging # lambda
42
+ attr_accessor :logging_files
43
+
44
+ # Paging
45
+ attr_accessor :per_page
46
+
47
+ def initialize
48
+ self.solo_mode = true
49
+
50
+ self.owner_model = defined?(::User) ? 'User' : nil
51
+ self.owner_method = :current_user
52
+ self.owner_name_method = :name
53
+ self.owner_avatar = ->(_) { nil }
54
+
55
+ self.file_upload = false
56
+ self.acceptable_file_types = DEFAULT_FILE_TYPES
57
+ self.file_uploader = ::RailsExecution::Files::Uploader
58
+ self.file_reader = ::RailsExecution::Files::Reader
59
+ self.per_page = DEFAULT_PER_PAGE
60
+ self.reviewers = -> { [] }
61
+
62
+ self.task_creatable = -> (_user) { true }
63
+ self.task_editable = -> (_user, _task) { true }
64
+ self.task_closable = -> (_user, _task) { true }
65
+ self.task_approvable = -> (_user, _task) { true }
66
+ self.task_executable = -> (_user, _task) { true }
67
+
68
+ self.logging = -> (_log_file, _task) { }
69
+ self.logging_files = -> (_task) { [] }
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'haml'
4
+
5
+ module RailsExecution
6
+ class Engine < ::Rails::Engine
7
+
8
+ isolate_namespace ::RailsExecution
9
+
10
+ initializer 'execution' do |app|
11
+ if defined?(Sprockets)
12
+ app.config.assets.precompile << 'executions/base.js'
13
+ app.config.assets.precompile << 'executions/base.css'
14
+ app.config.assets.precompile << 'executions/logo.png'
15
+ app.config.assets.precompile << 'executions/robot.png'
16
+ app.config.assets.precompile << 'executions/favicon.png'
17
+ app.config.assets.precompile << 'executions/rejected.png'
18
+ app.config.assets.precompile << 'executions/approved.png'
19
+ app.config.assets.precompile << 'executions/bootstrap-icons.woff' # Icons font
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsExecution
4
+ class Error < StandardError; end
5
+ class AccessDeniedError < Error; end
6
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'tempfile'
5
+ require 'uri'
6
+
7
+ module RailsExecution
8
+ module Files
9
+ class Reader
10
+
11
+ def initialize(task)
12
+ @task = task
13
+ @tempfile_by_name = {}
14
+ end
15
+
16
+ def call
17
+ raise ::NotImplementedError, "load_files (Output: { 'file1' => 'file-url', file2 => 'file-url' })"
18
+ end
19
+
20
+ # Return: the Tempfile instance
21
+ def get_file(name)
22
+ return nil if name.blank?
23
+
24
+ @file_url_by_name ||= call
25
+ file_url = @file_url_by_name[name]
26
+ return nil if file_url.blank?
27
+
28
+ @tempfile_by_name[name] ||= save_to_tempfile(name, file_url)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :task
34
+
35
+ def save_to_tempfile(file_name, url)
36
+ file_extension = URI(url).path.split('.').last
37
+ file_extension = ".#{file_extension}" if file_extension
38
+ tmp_file = Tempfile.new([file_name, file_extension])
39
+ tmp_file.binmode
40
+ open(url) do |url_file|
41
+ tmp_file.write(url_file.read)
42
+ end
43
+
44
+ return tmp_file
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsExecution
4
+ module Files
5
+ class Uploader
6
+
7
+ def initialize(task, inputs, owner: nil)
8
+ @task = task
9
+ @files = files_filter(inputs).compact
10
+ @owner = owner
11
+ end
12
+
13
+ def call
14
+ raise ::NotImplementedError, 'File uploader'
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :task
20
+ attr_reader :files
21
+ attr_reader :owner
22
+
23
+ def files_filter(inputs)
24
+ inputs.map do |_multiple, attachment|
25
+ next if attachment['name'].blank?
26
+ next if attachment['file'].blank?
27
+ next unless attachment['file'].content_type.in?(acceptable_file_types)
28
+
29
+ OpenStruct.new({
30
+ name: attachment['name'],
31
+ file: attachment['file'],
32
+ })
33
+ end
34
+ end
35
+
36
+ def acceptable_file_types
37
+ ::RailsExecution.configuration.acceptable_file_types.values
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ module RailsExecution
2
+ module Services
3
+ class Approvement
4
+
5
+ def initialize(task, reviewer: nil)
6
+ @task = task || (raise 'task is blank')
7
+ @reviewer = reviewer || (raise 'reviewer is blank')
8
+ end
9
+
10
+ def approve
11
+ task.update(status: :approved)
12
+ review.update(status: :approved)
13
+ add_activity('approved')
14
+ end
15
+
16
+ def reject
17
+ review.update(status: :rejected)
18
+ task.update(status: :rejected) if make_task_to_rejected?
19
+ add_activity('rejected')
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :task
25
+ attr_reader :reviewer
26
+
27
+ def review
28
+ @review ||= task.task_reviews.find_or_initialize_by(owner: reviewer)
29
+ end
30
+
31
+ def add_activity(status)
32
+ task.activities.create(owner: reviewer, message: "#{status.titleize} the task")
33
+ end
34
+
35
+ def make_task_to_rejected?
36
+ task.task_reviews.is_approved.empty?
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ module RailsExecution
2
+ module Services
3
+ class Execution
4
+
5
+ def self.call(task_id)
6
+ self.new(::RailsExecution::Task.find(task_id)).call
7
+ end
8
+
9
+ def initialize(task)
10
+ @task = task
11
+ end
12
+
13
+ def call
14
+ return if bad_syntax?
15
+
16
+ build_execution_file!
17
+ load_execution_file!
18
+ is_successful = false
19
+ begin
20
+ setup_logger!
21
+ execute_class!
22
+ is_successful = true
23
+ rescue
24
+ is_successful = false
25
+ ensure
26
+ restore_logger!
27
+ storing_log_file!
28
+ return is_successful
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :task
35
+ attr_reader :class_name
36
+
37
+ def bad_syntax?
38
+ !::RailsExecution::Services::SyntaxChecker.new(task.script).call
39
+ end
40
+
41
+ def build_execution_file!
42
+ @class_name = "RailsExecutionID#{task.id}Time#{task.updated_at.to_i}Executor"
43
+ ruby_code = <<~RUBY
44
+ class #{class_name} < ::RailsExecution::Services::Executor
45
+ def call
46
+ task.with_lock do
47
+ #{task.script}
48
+ end
49
+ end
50
+ end
51
+ RUBY
52
+
53
+ @file = ::Tempfile.new([class_name, '.rb'])
54
+ @file.binmode
55
+ @file.write(ruby_code)
56
+ @file.flush
57
+ @file
58
+ end
59
+
60
+ def load_execution_file!
61
+ load @file.path
62
+ end
63
+
64
+ def setup_logger!
65
+ @model_logger = ::ActiveRecord::Base.logger
66
+ @rails_logger = ::Rails.logger
67
+ @tempfile = ::Tempfile.new(["rails_execution_#{Time.current.strftime('%Y%m%d_%H%M%S')}", '.log'])
68
+ ::ActiveRecord::Base.logger = ::Logger.new(@tempfile.path)
69
+ ::Rails.logger = ::ActiveRecord::Base.logger
70
+ end
71
+
72
+ def execute_class!
73
+ class_name.constantize.new(task).call
74
+ end
75
+
76
+ def storing_log_file!
77
+ ::RailsExecution.configuration.logging.call(@tempfile, task)
78
+ end
79
+
80
+ def restore_logger!
81
+ ::ActiveRecord::Base.logger = @model_logger
82
+ ::Rails.logger = @rails_logger
83
+ end
84
+
85
+ end
86
+ end
87
+ end