joblin 0.1.4 → 0.1.5
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/models/joblin/background_task/api_access.rb +119 -115
- data/app/models/joblin/background_task/attachments.rb +38 -36
- data/app/models/joblin/background_task/executor.rb +60 -58
- data/app/models/joblin/background_task/options.rb +4 -3
- data/app/models/joblin/background_task/retention_policy.rb +19 -17
- data/app/models/joblin/background_task.rb +8 -4
- data/db/migrate/20250916184852_add_results_to_joblin_background_tasks.rb +6 -0
- data/lib/joblin/version.rb +1 -1
- metadata +2 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6cb0e0c2038030615d4e6a50e08aac0166f022a6af89f2a5d157d977e2f03f58
         | 
| 4 | 
            +
              data.tar.gz: f25744584d54054fe566770b10870159ac9c2a422c8e44cb609aca67116f4c03
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 726db8abb51ae0697aa6299df1179c7ef7a5e7de96681b6da9341d97242d4a39928ca4a274a26d58b35ba519f29c3b7d18b8d19dc4cf66bbdd7f25f0c5f83a2b
         | 
| 7 | 
            +
              data.tar.gz: 83ca76bd6c338207259f08e4115920613c525c5bbe3be431a6a1299bfc0b79282f69a986ce3c04fa17078b0c2dce24f1de1e8556e8d371bda03bcb7231c5ba15
         | 
| @@ -1,155 +1,159 @@ | |
| 1 1 | 
             
            module Joblin
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
                   | 
| 7 | 
            -
                     | 
| 8 | 
            -
             | 
| 9 | 
            -
                       | 
| 10 | 
            -
                        aar | 
| 2 | 
            +
              class BackgroundTask
         | 
| 3 | 
            +
                module ApiAccess
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  class_methods do
         | 
| 7 | 
            +
                    def api_access_rules(&blk)
         | 
| 8 | 
            +
                      @api_access_rules ||= []
         | 
| 9 | 
            +
                      if blk
         | 
| 10 | 
            +
                        @api_access_rules << ApiAccessRules.new.tap do |aar|
         | 
| 11 | 
            +
                          aar.instance_exec(&blk)
         | 
| 12 | 
            +
                        end
         | 
| 13 | 
            +
                        nil
         | 
| 14 | 
            +
                      else
         | 
| 15 | 
            +
                        [*superclass.try(:api_access_rules), *@api_access_rules].compact.freeze
         | 
| 11 16 | 
             
                      end
         | 
| 12 | 
            -
                      nil
         | 
| 13 | 
            -
                    else
         | 
| 14 | 
            -
                      [*superclass.try(:api_access_rules), *@api_access_rules].compact.freeze
         | 
| 15 17 | 
             
                    end
         | 
| 16 | 
            -
                  end
         | 
| 17 18 |  | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                  def api_access_allowed?
         | 
| 25 | 
            -
                    # This is intentionally not inherited by subclasses
         | 
| 26 | 
            -
                    @api_access_allowed
         | 
| 27 | 
            -
                  end
         | 
| 19 | 
            +
                    def allow_api_access!(&blk)
         | 
| 20 | 
            +
                      include Mixin unless self < Mixin
         | 
| 21 | 
            +
                      @api_access_allowed = true
         | 
| 22 | 
            +
                      api_access_rules(&blk) if blk
         | 
| 23 | 
            +
                    end
         | 
| 28 24 |  | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
                     | 
| 33 | 
            -
                  end
         | 
| 25 | 
            +
                    def api_access_allowed?
         | 
| 26 | 
            +
                      # This is intentionally not inherited by subclasses
         | 
| 27 | 
            +
                      @api_access_allowed
         | 
| 28 | 
            +
                    end
         | 
| 34 29 |  | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                       | 
| 38 | 
            -
                       | 
| 39 | 
            -
                    else
         | 
| 40 | 
            -
                      params ||= controller_or_params
         | 
| 30 | 
            +
                    def find_for_api(id)
         | 
| 31 | 
            +
                      task = find(id)
         | 
| 32 | 
            +
                      raise ActiveRecord::RecordNotFound unless task.api_access_allowed?
         | 
| 33 | 
            +
                      task
         | 
| 41 34 | 
             
                    end
         | 
| 42 35 |  | 
| 43 | 
            -
                     | 
| 44 | 
            -
             | 
| 36 | 
            +
                    def build_from_api(controller_or_params, options = {}, params: nil, exec_context: nil)
         | 
| 37 | 
            +
                      if controller_or_params.respond_to?(:params)
         | 
| 38 | 
            +
                        exec_context ||= controller_or_params
         | 
| 39 | 
            +
                        params ||= controller_or_params.params.require(:background_task)
         | 
| 40 | 
            +
                      else
         | 
| 41 | 
            +
                        params ||= controller_or_params
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      task_type = self == BackgroundTask ? params[:type].safe_constantize : self
         | 
| 45 | 
            +
                      raise ActiveRecord::RecordNotFound unless task_type && task_type <= BackgroundTask
         | 
| 45 46 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 47 | 
            +
                      task = task_type.new
         | 
| 48 | 
            +
                      raise ActiveRecord::RecordNotFound unless task.api_access_allowed?
         | 
| 49 | 
            +
                      rule_sets = task_type.api_access_rules
         | 
| 49 50 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 51 | 
            +
                      # Apply permitted options
         | 
| 52 | 
            +
                      if params[:options].present?
         | 
| 53 | 
            +
                        permitted_options = rule_sets.map(&:permitted_options).flatten
         | 
| 54 | 
            +
                        task.options.merge!(params[:options].permit(permitted_options).to_h)
         | 
| 55 | 
            +
                      end
         | 
| 55 56 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 57 | 
            +
                      # Apply default options
         | 
| 58 | 
            +
                      rule_sets.reverse.each do |aar|
         | 
| 59 | 
            +
                        aar.default_options.each do |k, v|
         | 
| 60 | 
            +
                          next if task.options.key?(k)
         | 
| 61 | 
            +
                          task.options[k] = v.is_a?(Proc) ? exec_context.instance_exec(&v) : v
         | 
| 62 | 
            +
                        end
         | 
| 61 63 | 
             
                      end
         | 
| 62 | 
            -
                    end
         | 
| 63 64 |  | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 65 | 
            +
                      # Apply any additional/hard-coded options
         | 
| 66 | 
            +
                      task.options.merge!(options)
         | 
| 66 67 |  | 
| 67 | 
            -
             | 
| 68 | 
            +
                      raise ActiveRecord::RecordInvalid.new(task) unless task.api_access_allowed?
         | 
| 68 69 |  | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 70 | 
            +
                      val_errors = task.api_validate_options
         | 
| 71 | 
            +
                      raise ActiveRecord::ValidationError.new(val_errors) unless val_errors.empty?
         | 
| 71 72 |  | 
| 72 | 
            -
             | 
| 73 | 
            +
                      task
         | 
| 74 | 
            +
                    end
         | 
| 73 75 | 
             
                  end
         | 
| 74 | 
            -
                end
         | 
| 75 76 |  | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 77 | 
            +
                  included do
         | 
| 78 | 
            +
                    api_access_rules do
         | 
| 79 | 
            +
                      default_option(:creator) { try(:current_user) }
         | 
| 80 | 
            +
                    end
         | 
| 79 81 | 
             
                  end
         | 
| 80 | 
            -
                end
         | 
| 81 82 |  | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 83 | 
            +
                  def api_access_allowed?
         | 
| 84 | 
            +
                    self.class.api_access_allowed?
         | 
| 85 | 
            +
                  end
         | 
| 85 86 |  | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 87 | 
            +
                  module Mixin
         | 
| 88 | 
            +
                    extend ActiveSupport::Concern
         | 
| 88 89 |  | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 90 | 
            +
                    included do
         | 
| 91 | 
            +
                      begin
         | 
| 92 | 
            +
                        ::BackgroundTaskChannel
         | 
| 93 | 
            +
                        after_commit do
         | 
| 94 | 
            +
                          ::BackgroundTaskChannel.broadcast_to(self, api_serialize) if api_access_allowed?
         | 
| 95 | 
            +
                        end
         | 
| 96 | 
            +
                      rescue NameError
         | 
| 94 97 | 
             
                      end
         | 
| 95 | 
            -
                    rescue NameError
         | 
| 96 98 | 
             
                    end
         | 
| 97 | 
            -
                  end
         | 
| 98 99 |  | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 100 | 
            +
                    def api_serialize
         | 
| 101 | 
            +
                      builder = Jbuilder.new do |json|
         | 
| 102 | 
            +
                        json.id id
         | 
| 103 | 
            +
                        json.type type
         | 
| 104 | 
            +
                        json.workflow_state workflow_state
         | 
| 104 105 |  | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                         | 
| 106 | 
            +
                        json.error_message error_message || "Internal Error" if workflow_state == 'failed'
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                        rule_sets = self.class.api_access_rules
         | 
| 109 | 
            +
                        rule_sets.each do |aar|
         | 
| 110 | 
            +
                          instance_exec(json, &aar.serializer) if aar.serializer
         | 
| 111 | 
            +
                        end
         | 
| 108 112 | 
             
                      end
         | 
| 109 | 
            -
                    end
         | 
| 110 113 |  | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 114 | 
            +
                      builder.attributes!
         | 
| 115 | 
            +
                    end
         | 
| 113 116 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 117 | 
            +
                    def api_validate_options
         | 
| 118 | 
            +
                      errors = []
         | 
| 119 | 
            +
                      rule_sets = self.class.api_access_rules
         | 
| 120 | 
            +
                      rule_sets.each do |aar|
         | 
| 121 | 
            +
                        aar.validators.each do |validator|
         | 
| 122 | 
            +
                          errors << instance_exec(&validator)
         | 
| 123 | 
            +
                        end
         | 
| 120 124 | 
             
                      end
         | 
| 125 | 
            +
                      errors.flatten.compact.uniq
         | 
| 121 126 | 
             
                    end
         | 
| 122 | 
            -
                    errors.flatten.compact.uniq
         | 
| 123 127 | 
             
                  end
         | 
| 124 | 
            -
                end
         | 
| 125 128 |  | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 129 | 
            +
                  class ApiAccessRules
         | 
| 130 | 
            +
                    attr_accessor :serializer
         | 
| 131 | 
            +
                    attr_reader :validators
         | 
| 132 | 
            +
                    attr_reader :default_options
         | 
| 133 | 
            +
                    attr_reader :permitted_options
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    def initialize
         | 
| 136 | 
            +
                      @serializer = nil
         | 
| 137 | 
            +
                      @validators = []
         | 
| 138 | 
            +
                      @permitted_options = []
         | 
| 139 | 
            +
                      @default_options = {}
         | 
| 140 | 
            +
                    end
         | 
| 138 141 |  | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            +
                    def default_option(key, value = nil, &blk)
         | 
| 143 | 
            +
                      @default_options[key] = blk || value
         | 
| 144 | 
            +
                    end
         | 
| 142 145 |  | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 146 | 
            +
                    def permit_options(*args)
         | 
| 147 | 
            +
                      @permitted_options.concat(args)
         | 
| 148 | 
            +
                    end
         | 
| 146 149 |  | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            +
                    def validate(&blk)
         | 
| 151 | 
            +
                      @validators << blk
         | 
| 152 | 
            +
                    end
         | 
| 150 153 |  | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 154 | 
            +
                    def serialize(&blk)
         | 
| 155 | 
            +
                      @serializer = blk
         | 
| 156 | 
            +
                    end
         | 
| 153 157 | 
             
                  end
         | 
| 154 158 | 
             
                end
         | 
| 155 159 | 
             
              end
         | 
| @@ -1,49 +1,51 @@ | |
| 1 1 | 
             
            module Joblin
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
                   | 
| 9 | 
            -
                     | 
| 10 | 
            -
                       | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 2 | 
            +
              class BackgroundTask
         | 
| 3 | 
            +
                module Attachments
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  include Joblin::Concerns::JobWorkingDirs
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def attachment_path(key, expires_in: true)
         | 
| 9 | 
            +
                    if !expires_in || Rails.env.development? || Rails.env.test?
         | 
| 10 | 
            +
                      return Rails.application.routes.url_helpers.rails_blob_path(
         | 
| 11 | 
            +
                        send(key),
         | 
| 12 | 
            +
                        only_path: true,
         | 
| 13 | 
            +
                        disposition: 'attachment',
         | 
| 14 | 
            +
                        # organization_id: current_organization&.id,
         | 
| 15 | 
            +
                      )
         | 
| 16 | 
            +
                    end
         | 
| 16 17 |  | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 18 | 
            +
                    if expires_in == true
         | 
| 19 | 
            +
                      send(key).url
         | 
| 20 | 
            +
                    else expires_in
         | 
| 21 | 
            +
                      send(key).url(expires_in:)
         | 
| 22 | 
            +
                    end
         | 
| 21 23 | 
             
                  end
         | 
| 22 | 
            -
                end
         | 
| 23 24 |  | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 25 | 
            +
                  def attach_file(key, path, as: nil)
         | 
| 26 | 
            +
                    path = File.join(working_dir, path) unless Pathname.new(path).absolute?
         | 
| 26 27 |  | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 28 | 
            +
                    self.send(key).attach(
         | 
| 29 | 
            +
                      io: File.open(path),
         | 
| 30 | 
            +
                      filename: as || File.basename(path)
         | 
| 31 | 
            +
                    )
         | 
| 32 | 
            +
                  end
         | 
| 32 33 |  | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 34 | 
            +
                  def load_attachment(key, save_as: nil)
         | 
| 35 | 
            +
                    @loaded_attachments ||= {}
         | 
| 35 36 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 37 | 
            +
                    @loaded_attachments[key] ||= begin
         | 
| 38 | 
            +
                      save_as = save_as || send(key).filename.to_s || key.to_s
         | 
| 39 | 
            +
                      save_as = File.join(working_dir, save_as) unless Pathname.new(save_as).absolute?
         | 
| 39 40 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 41 | 
            +
                      File.open(save_as, 'w') do |file|
         | 
| 42 | 
            +
                        send(key).download do |chunk|
         | 
| 43 | 
            +
                          file.write(chunk.force_encoding("UTF-8"))
         | 
| 44 | 
            +
                        end
         | 
| 43 45 | 
             
                      end
         | 
| 46 | 
            +
                      save_as
         | 
| 44 47 | 
             
                    end
         | 
| 45 | 
            -
                    save_as
         | 
| 46 48 | 
             
                  end
         | 
| 47 49 | 
             
                end
         | 
| 48 50 | 
             
              end
         | 
| 49 | 
            -
            end
         | 
| 51 | 
            +
            end
         | 
| @@ -1,79 +1,81 @@ | |
| 1 1 | 
             
            module Joblin
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 2 | 
            +
              class BackgroundTask
         | 
| 3 | 
            +
                module Executor
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 4 5 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 6 | 
            +
                  class_methods do
         | 
| 7 | 
            +
                    def inherited(subclass)
         | 
| 8 | 
            +
                      subclass.const_set(:ExecutorJob, Class.new(self::ExecutorJob))
         | 
| 9 | 
            +
                      super
         | 
| 10 | 
            +
                    end
         | 
| 10 11 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 12 | 
            +
                    def job_executor_class
         | 
| 13 | 
            +
                      self::ExecutorJob
         | 
| 14 | 
            +
                    end
         | 
| 14 15 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 16 | 
            +
                    def executor_eval(&blk)
         | 
| 17 | 
            +
                      job_executor_class.class_eval(&blk)
         | 
| 18 | 
            +
                    end
         | 
| 18 19 |  | 
| 19 | 
            -
             | 
| 20 | 
            +
                    delegate :queue_as, :sidekiq_options, to: :job_executor_class
         | 
| 20 21 |  | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 22 | 
            +
                    %i[before around after].each do |hook_type|
         | 
| 23 | 
            +
                      define_method(:"#{hook_type}_perform") do |*args, &callback|
         | 
| 24 | 
            +
                        callback ||= args[0]
         | 
| 25 | 
            +
                        if callback.is_a?(Symbol) || callback.is_a?(String)
         | 
| 26 | 
            +
                          job_executor_class.define_method(callback) do |*margs, &blk|
         | 
| 27 | 
            +
                            the_task.send(callback, *margs, &blk)
         | 
| 28 | 
            +
                          end
         | 
| 29 | 
            +
                          job_executor_class.send(:"#{hook_type}_perform", callback)
         | 
| 30 | 
            +
                        elsif callback.is_a?(Proc)
         | 
| 31 | 
            +
                          # Not exactly pretty...
         | 
| 32 | 
            +
                          proc = case callback.arity
         | 
| 33 | 
            +
                          when 0
         | 
| 34 | 
            +
                            -> { the_task.instance_exec(&callback) }
         | 
| 35 | 
            +
                          when 1
         | 
| 36 | 
            +
                            ->(a) { the_task.instance_exec(a, &callback) }
         | 
| 37 | 
            +
                          when 2
         | 
| 38 | 
            +
                            ->(a, b) { the_task.instance_exec(a, b, &callback) }
         | 
| 39 | 
            +
                          else
         | 
| 40 | 
            +
                            raise ArgumentError, "Unsupported callback arity #{callback.arity}"
         | 
| 41 | 
            +
                          end
         | 
| 42 | 
            +
                          job_executor_class.send(:"#{hook_type}_perform", &proc)
         | 
| 40 43 | 
             
                        end
         | 
| 41 | 
            -
                        job_executor_class.send(:"#{hook_type}_perform", &proc)
         | 
| 42 44 | 
             
                      end
         | 
| 43 45 | 
             
                    end
         | 
| 44 | 
            -
                  end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                  # delegate :set, to: :job_executor_class
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                  # delegate :before_perform, :around_perform, :after_perform, to: :job_executor_class
         | 
| 49 | 
            -
                  # delegate :before_enqueue, :around_enqueue, :after_enqueue, to: :job_executor_class
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                included do
         | 
| 53 | 
            -
                  delegate_missing_to :_current_executor
         | 
| 54 | 
            -
                end
         | 
| 55 46 |  | 
| 56 | 
            -
             | 
| 47 | 
            +
                    # delegate :set, to: :job_executor_class
         | 
| 57 48 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 49 | 
            +
                    # delegate :before_perform, :around_perform, :after_perform, to: :job_executor_class
         | 
| 50 | 
            +
                    # delegate :before_enqueue, :around_enqueue, :after_enqueue, to: :job_executor_class
         | 
| 51 | 
            +
                  end
         | 
| 61 52 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 53 | 
            +
                  included do
         | 
| 54 | 
            +
                    delegate_missing_to :_current_executor
         | 
| 55 | 
            +
                  end
         | 
| 64 56 |  | 
| 65 | 
            -
                   | 
| 66 | 
            -
                    the_task.update(workflow_state: "started") if the_task.workflow_state == "scheduled"
         | 
| 57 | 
            +
                  protected
         | 
| 67 58 |  | 
| 68 | 
            -
             | 
| 59 | 
            +
                  def _current_executor
         | 
| 60 | 
            +
                    @executor
         | 
| 69 61 | 
             
                  end
         | 
| 70 62 |  | 
| 71 | 
            -
                   | 
| 72 | 
            -
                     | 
| 73 | 
            -
                       | 
| 63 | 
            +
                  class ExecutorJob < ActiveJob::Base
         | 
| 64 | 
            +
                    def perform
         | 
| 65 | 
            +
                      the_task.update(workflow_state: "started") if the_task.workflow_state == "scheduled"
         | 
| 66 | 
            +
                      the_task.perform
         | 
| 67 | 
            +
                    rescue InvalidJobError => ex
         | 
| 68 | 
            +
                      the_task.error_message ||= ex.message
         | 
| 69 | 
            +
                      the_task.workflow_state = "failed"
         | 
| 70 | 
            +
                      the_task.save! if the_task.changed?
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def the_task
         | 
| 74 | 
            +
                      @the_task ||= BackgroundTask.find(batch_context[:background_task_id]).tap do |task|
         | 
| 75 | 
            +
                        task.instance_variable_set(:@executor, self)
         | 
| 76 | 
            +
                      end
         | 
| 74 77 | 
             
                    end
         | 
| 75 78 | 
             
                  end
         | 
| 76 79 | 
             
                end
         | 
| 77 | 
            -
             | 
| 78 80 | 
             
              end
         | 
| 79 81 | 
             
            end
         | 
| @@ -1,5 +1,5 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
              module  | 
| 1 | 
            +
            class Joblin::BackgroundTask
         | 
| 2 | 
            +
              module Options
         | 
| 3 3 | 
             
                extend ActiveSupport::Concern
         | 
| 4 4 |  | 
| 5 5 | 
             
                class_methods do
         | 
| @@ -16,7 +16,8 @@ module Joblin | |
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 18 | 
             
                included do
         | 
| 19 | 
            -
                  serialize : | 
| 19 | 
            +
                  serialize :results, coder: Joblin::LazyAccess
         | 
| 20 | 
            +
                  serialize :extra_options, coder: Joblin::LazyAccess
         | 
| 20 21 |  | 
| 21 22 | 
             
                  after_initialize do
         | 
| 22 23 | 
             
                    self.extra_options ||= HashWithIndifferentAccess.new
         | 
| @@ -1,26 +1,28 @@ | |
| 1 1 | 
             
            module Joblin
         | 
| 2 | 
            -
               | 
| 3 | 
            -
                 | 
| 2 | 
            +
              class BackgroundTask
         | 
| 3 | 
            +
                module RetentionPolicy
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 4 5 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 6 | 
            +
                  class_methods do
         | 
| 7 | 
            +
                    def record_retention(policy = nil)
         | 
| 8 | 
            +
                      if policy.nil?
         | 
| 9 | 
            +
                        @record_retention || superclass.try(:record_retention)
         | 
| 10 | 
            +
                      else
         | 
| 11 | 
            +
                        @record_retention = policy
         | 
| 12 | 
            +
                      end
         | 
| 11 13 | 
             
                    end
         | 
| 12 14 | 
             
                  end
         | 
| 13 | 
            -
                end
         | 
| 14 15 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 16 | 
            +
                  class BackgroundTaskCleaner < ActiveJob::Base
         | 
| 17 | 
            +
                    def perform
         | 
| 18 | 
            +
                      types = BackgroundTask.distinct.pluck(:type)
         | 
| 19 | 
            +
                      types.each do |type|
         | 
| 20 | 
            +
                        type = type.constantize
         | 
| 21 | 
            +
                        rp = type.record_retention
         | 
| 22 | 
            +
                        next unless rp
         | 
| 22 23 |  | 
| 23 | 
            -
             | 
| 24 | 
            +
                        type.where("created_at < ?", rp.ago).destroy_all
         | 
| 25 | 
            +
                      end
         | 
| 24 26 | 
             
                    end
         | 
| 25 27 | 
             
                  end
         | 
| 26 28 | 
             
                end
         | 
| @@ -6,8 +6,12 @@ module Joblin | |
| 6 6 | 
             
                include Attachments
         | 
| 7 7 | 
             
                include ApiAccess
         | 
| 8 8 |  | 
| 9 | 
            +
                class InvalidJobError < StandardError; end
         | 
| 10 | 
            +
             | 
| 9 11 | 
             
                belongs_to :creator, polymorphic: true, optional: true
         | 
| 10 12 |  | 
| 13 | 
            +
                validates :workflow_state, inclusion: { in: %w[unscheduled scheduled started completed failed cancelled] }
         | 
| 14 | 
            +
             | 
| 11 15 | 
             
                after_initialize do
         | 
| 12 16 | 
             
                  self.workflow_state ||= 'unscheduled'
         | 
| 13 17 | 
             
                end
         | 
| @@ -61,20 +65,20 @@ module Joblin | |
| 61 65 | 
             
                    tracker = BackgroundTask.find(options['id'])
         | 
| 62 66 | 
             
                    return if tracker.workflow_state == 'cancelled'
         | 
| 63 67 |  | 
| 64 | 
            -
                    tracker.workflow_state  | 
| 65 | 
            -
                    tracker.save! if tracker.changed?
         | 
| 68 | 
            +
                    # tracker.update!(workflow_state: 'completed') if status.success?
         | 
| 66 69 | 
             
                  end
         | 
| 67 70 |  | 
| 68 71 | 
             
                  def on_success(status, options)
         | 
| 69 72 | 
             
                    tracker = BackgroundTask.find(options['id'])
         | 
| 70 73 | 
             
                    return if tracker.workflow_state == 'cancelled'
         | 
| 74 | 
            +
                    return if tracker.workflow_state == 'failed'
         | 
| 71 75 |  | 
| 72 | 
            -
                    tracker.workflow_state  | 
| 73 | 
            -
                    tracker.save! if tracker.changed?
         | 
| 76 | 
            +
                    tracker.update!(workflow_state: 'completed')
         | 
| 74 77 | 
             
                  end
         | 
| 75 78 |  | 
| 76 79 | 
             
                  def on_stagnated(status, options)
         | 
| 77 80 | 
             
                    tracker = BackgroundTask.find(options['id'])
         | 
| 81 | 
            +
                    tracker.update(workflow_state: 'failed') if tracker.workflow_state != 'cancelled'
         | 
| 78 82 | 
             
                    tracker.handle_batch_stagnation
         | 
| 79 83 | 
             
                  end
         | 
| 80 84 | 
             
                end
         | 
    
        data/lib/joblin/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: joblin
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.5
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ethan Knapp
         | 
| @@ -156,6 +156,7 @@ files: | |
| 156 156 | 
             
            - app/models/joblin/background_task/retention_policy.rb
         | 
| 157 157 | 
             
            - app/models/joblin/concerns/job_working_dirs.rb
         | 
| 158 158 | 
             
            - db/migrate/20250903184852_create_joblin_background_tasks.rb
         | 
| 159 | 
            +
            - db/migrate/20250916184852_add_results_to_joblin_background_tasks.rb
         | 
| 159 160 | 
             
            - joblin.gemspec
         | 
| 160 161 | 
             
            - lib/joblin.rb
         | 
| 161 162 | 
             
            - lib/joblin/batching/batch.rb
         |