Rails app template

Solid Queue

Add Avo configuration files to manage your Solid Queue jobs

The main selling point of Avo for me was the speed of being able to build a simple CRUD interface. I like the customizability of the system, which is something I would take heavy advantage of
Nathan Kriege
Nathan Kriege
Ruby on Rails developer
Run the following command to quickly apply this app template

Do you think this template needs changes? Submit a PR here.

Full template

# Template info:
#   Link: https://avohq.io/templates/solid-queue
#   Repository: https://github.com/avo-hq/solid_queue-app-template

# List of all Avo files with path and contents
files = {"app/avo/actions/retry.rb"=>"class Avo::Actions::Retry < Avo::BaseAction\n  self.name = \"Retry\"\n\n  def handle(query:, fields:, current_user:, resource:, **args)\n    query.each do |record|\n      record.retry\n    end\n  end\nend\n", "app/avo/cards/failed_jobs.rb"=>"class Avo::Cards::FailedJobs < Avo::Cards::MetricCard\n  self.id = \"failed_count\"\n  self.label = \"Failed count\"\n\n  def query\n    result SolidQueue::FailedExecution.count\n  end\nend\n", "app/avo/cards/jobs_count.rb"=>"class Avo::Cards::JobsCount < Avo::Cards::MetricCard\n  self.id = \"jobs_count\"\n  self.label = \"Jobs count\"\n\n  def query\n    type = arguments.fetch :type\n\n    query = SolidQueue::Job.all\n\n    if type == :scheduled\n      query = query.finished.invert_where\n    end\n\n    result query.length\n  end\nend\n", "app/avo/cards/processes.rb"=>"class Avo::Cards::Processes < Avo::Cards::PartialCard\n  self.id = \"processes\"\n  self.label = \"Processes\"\n  self.partial = \"avo/cards/processes\"\n\n  def processes\n    SolidQueue::Process.all\n  end\nend\n\n", "app/avo/cards/queues.rb"=>"class Avo::Cards::Queues < Avo::Cards::PartialCard\n  self.id = \"queues\"\n  self.label = \"Queues\"\n  self.partial = \"avo/cards/queues\"\n\n  def queues\n    SolidQueue::Queue.all\n  end\nend\n", "app/avo/dashboards/solid_queue.rb"=>"class Avo::Dashboards::SolidQueue < Avo::Dashboards::BaseDashboard\n  self.id = \"solid_queue\"\n  self.name = \"Solid queue\"\n\n  def cards\n    card Avo::Cards::JobsCount,\n      label: \"Scheduled jobs\",\n      arguments: {\n        type: :scheduled\n      }\n    card Avo::Cards::JobsCount,\n      label: \"Total jobs\",\n      arguments: {\n        type: :total_jobs\n      }\n    card Avo::Cards::FailedJobs\n\n    divider label: \"Queues information\"\n\n    card Avo::Cards::Queues, cols: 3\n\n    divider label: \"Processes information\"\n\n    card Avo::Cards::Processes, cols: 3\n  end\nend\n", "app/avo/resources/solid_queue_failed_execution.rb"=>"class Avo::Resources::SolidQueueFailedExecution < Avo::BaseResource\n  self.includes = []\n  self.model_class = \"SolidQueue::FailedExecution\"\n  self.authorization_policy = SolidQueue::FailedExecutionPolicy\n  TITLE_BLOCK = -> { \"\#{record.job.class_name} - \#{record&.error.fetch(\"exception_class\")} '\#{record&.error&.fetch(\"message\")}'\" }\n  self.title = TITLE_BLOCK\n\n  self.row_controls = -> {\n    action Avo::Actions::Retry\n  }\n\n  def fields\n    field :id, as: :id\n    field :title, as: :text, &TITLE_BLOCK\n    field :error, as: :code, language: \"javascript\" do\n      if record.error.present?\n        JSON.pretty_generate(record.error.as_json)\n      end\n    end\n    field :job, as: :belongs_to\n  end\n\n  def actions\n    action Avo::Actions::Retry\n  end\nend\n", "app/avo/resources/solid_queue_job.rb"=>"class Avo::Resources::SolidQueueJob < Avo::BaseResource\n  self.includes = []\n  self.title = :class_name\n  self.model_class = \"SolidQueue::Job\"\n  self.authorization_policy = SolidQueue::JobPolicy\n\n  def fields\n    main_panel do\n      field :id, as: :id\n      field :queue_name, as: :text\n      field :class_name, as: :text\n      field :arguments_name, as: :code\n      field :priority, as: :number\n      field :scheduled_at, as: :date_time\n      field :finished_at, as: :date_time\n      field :queue_status, as: :badge, options: {success: :running, warning: :paused} do\n        job_queue = SolidQueue::Queue.all.find do |queue|\n          record.queue_name == queue.name\n        end\n\n        job_queue.paused? ? :paused : :running\n      end\n      sidebar do\n        field :active_job_id, as: :text\n        field :concurrency_key, as: :date_time\n      end\n    end\n\n    # field :claimed_execution, as: :has_one\n    # field :ready_execution, as: :has_one\n    field :failed_execution, as: :has_one\n    # has_one :ready_execution, dependent: :destroy\n    # has_one :claimed_execution, dependent: :destroy\n    # has_one :failed_execution, dependent: :destroy\n\n    # has_one :scheduled_execution, dependent: :destroy\n  end\n\n  def scopes\n    scope Avo::Scopes::Finished\n    scope Avo::Scopes::Scheduled\n    scope Avo::Scopes::Failed\n  end\n\n  def cards\n    if view.index?\n      card Avo::Cards::JobsCount,\n        label: \"Scheduled jobs\",\n        arguments: {\n          type: :scheduled\n        }\n      card Avo::Cards::JobsCount,\n        label: \"Total jobs\",\n        arguments: {\n          type: :total_jobs\n        }\n    end\n  end\nend\n", "app/avo/resources/solid_queue_process.rb"=>"class Avo::Resources::SolidQueueProcess < Avo::BaseResource\n  self.includes = []\n  self.model_class = \"SolidQueue::Process\"\n\n  def fields\n    field :id, as: :id\n    field :kind, as: :text\n    field :last_heartbeat_at, as: :date_time\n    field :supervisor_id, as: :number\n    field :pid, as: :number\n    field :hostname, as: :number\n    field :metadata, as: :code\n  end\nend\n", "app/avo/scopes/failed.rb"=>"class Avo::Scopes::Failed < Avo::Advanced::Scopes::BaseScope\n  self.name = \"Failed\"\n  self.description = \"Failed\"\n  self.scope = -> { query.includes(:failed_execution).where.not(failed_execution: {id: nil}) }\n  self.visible = -> { true }\nend\n", "app/avo/scopes/finished.rb"=>"class Avo::Scopes::Finished < Avo::Advanced::Scopes::BaseScope\n  self.name = \"Finished\"\n  self.description = \"Finished\"\n  self.scope = -> { query.finished }\n  self.visible = -> { true }\nend\n", "app/avo/scopes/paused.rb"=>"class Avo::Scopes::Paused < Avo::Advanced::Scopes::BaseScope\n  self.name = \"Paused\"\n  self.description = \"Paused\"\n  self.scope = -> { query.all }\n  self.visible = -> { true }\nend\n", "app/avo/scopes/scheduled.rb"=>"class Avo::Scopes::Scheduled < Avo::Advanced::Scopes::BaseScope\n  self.name = \"Scheduled\"\n  self.description = \"Scheduled\"\n  self.scope = -> { query.finished.invert_where }\n  self.visible = -> { true }\nend\n", "app/controllers/avo/queue_clears_controller.rb"=>"class Avo::QueueClearsController < ApplicationController\n  before_action :set_queue_by_name\n\n  def update\n    @queue.clear\n\n    redirect_to params[:frame_url]\n  end\n\n  private\n\n  def set_queue_by_name\n    @queue = SolidQueue::Queue.find_by_name params[:name]\n  end\nend\n", "app/controllers/avo/queue_visibilities_controller.rb"=>"class Avo::QueueVisibilitiesController < ApplicationController\n  before_action :set_queue_by_name\n\n  def update\n    action = params[:queue_action]\n\n    if action == \"pause\"\n      @queue.pause\n    elsif action == \"resume\"\n      @queue.resume\n    end\n\n    redirect_to params[:frame_url]\n  end\n\n  private\n\n  def set_queue_by_name\n    @queue = SolidQueue::Queue.find_by_name params[:name]\n  end\nend\n", "app/controllers/avo/solid_queue_failed_executions_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::SolidQueueFailedExecutionsController < Avo::ResourcesController\nend\n", "app/controllers/avo/solid_queue_jobs_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::SolidQueueJobsController < Avo::ResourcesController\nend\n", "app/controllers/avo/solid_queue_processes_controller.rb"=>"# This controller has been generated to enable Rails' resource routes.\n# More information on https://docs.avohq.io/3.0/controllers.html\nclass Avo::SolidQueueProcessesController < Avo::ResourcesController\nend\n", "app/policies/solid_queue/failed_execution_policy.rb"=>"class SolidQueue::FailedExecutionPolicy < ApplicationPolicy\n  def index?\n    true\n  end\n\n  def show?\n    true\n  end\n\n  def act_on?\n    true\n  end\n\n  def create?\n    false\n  end\n\n  def update?\n    false\n  end\n\n  def destroy?\n    false\n  end\n\n  class Scope < Scope\n    # NOTE: Be explicit about which records you allow access to!\n    def resolve\n      scope.all\n    end\n  end\nend\n", "app/policies/solid_queue/job_policy.rb"=>"class SolidQueue::JobPolicy < ApplicationPolicy\n  def index?\n    true\n  end\n\n  def show?\n    true\n  end\n\n  def create?\n    false\n  end\n\n  def destroy?\n    true\n  end\n\n  def create_failed_execution?\n    false\n  end\n\n  def attach_failed_execution?\n    false\n  end\n\n  def destroy_failed_execution?\n    false\n  end\n\n  class Scope < Scope\n    # NOTE: Be explicit about which records you allow access to!\n    def resolve\n      scope.all\n    end\n  end\nend\n", "app/policies/solid_queue/process_policy.rb"=>"class SolidQueue::ProcessPolicy < ApplicationPolicy\n  def index?\n    true\n  end\n\n  def show?\n    true\n  end\n\n  def create?\n    false\n  end\n\n  def update?\n    false\n  end\n\n  def destroy?\n    false\n  end\n\n  class Scope < Scope\n    def resolve\n      scope.all\n    end\n  end\nend\n", "app/views/avo/cards/_processes.html.erb"=>"<div class=\"flex-1 flex-initial mt-4 overflow-auto\">\n  <table class=\"w-full\">\n    <tbody class=\"divide-y\">\n      <% @card.processes.each do |process| %>\n        <tr class=\"hover:bg-blue-50\">\n          <td class=\"px-1 py-1\">\n            <div class=\"ml-3\">\n              <%= process.kind %>\n            </div>\n          </td>\n          <td class=\"px-1 py-1 text-sm text-right\">\n            <div class=\"mr-3 flex justify-end gap-2\">\n              <%= process.metadata %>\n            </div>\n          </td>\n          <td class=\"text-sm text-right\">\n            <div class=\"mr-3\">\n              <%= process.last_heartbeat_at %>\n            </div>\n          </td>\n        </tr>\n      <% end %>\n    </tbody>\n  </table>\n</div>\n", "app/views/avo/cards/_queues.html.erb"=>"<div class=\"flex-1 flex-initial mt-4 overflow-auto\">\n  <table class=\"w-full\">\n    <tbody class=\"divide-y\">\n      <% @card.queues.each do |queue| %>\n        <%\n          status = queue.paused? ? :paused : :running\n          label = status == :paused ? :resume : :pause\n        %>\n        <tr class=\"hover:bg-blue-50\">\n          <td class=\"px-1 py-1\">\n            <div class=\"ml-3\">\n              <%= queue.name %> -\n            <%= queue.size %> <%= \"job\".pluralize queue.size %>\n            </div>\n          </td>\n          <td class=\"px-1 py-1 text-sm text-right\">\n            <div class=\"mr-3 flex justify-end gap-2\">\n              <%= render Avo::Fields::Common::BadgeViewerComponent.new value: status, options: {success: :running, warning: :paused} %>\n            </div>\n          </td>\n          <td class=\"text-sm text-right\">\n            <div class=\"mr-3\">\n              <%= a_link avo.queue_visibility_path(name: queue.name, queue_action: label, frame_url: @card.frame_url), data: {turbo_method: :put}, size: :xs do %>\n                <%= label.to_s.humanize %>\n              <% end %>\n              <%= a_link avo.queue_clear_path(name: queue.name, queue_action: label, frame_url: @card.frame_url), data: {turbo_method: :put, turbo_confirm: \"Are you sure you want to delete all jobs from this queue?\"}, size: :xs, color: :red do %>\n                Clear queue\n              <% end %>\n            </div>\n          </td>\n        </tr>\n      <% end %>\n    </tbody>\n  </table>\n</div>\n"}

# === Add routes ===
route_contents = <<-ROUTES
  # Avo admin panel
  if defined? ::Avo
    Avo::Engine.routes.draw do
      resource :queue_visibility, only: :update
      resource :queue_clear, only: :update
    end
  end
ROUTES
route route_contents

# === Copy template files ===
files.each do |path, contents|
  file path, contents
end