Rails app template

Solid Queue

Add Avo configuration files to manage your Solid Queue jobs

Avo has enabled our team and client to collaborate in a much more seamless way.

Our favorite thing about Avo is the feature-loaded modern web interface and the ease of customizing from fields to pages is also impressive.

Santhana Karthikeyan
Santhana Karthikeyan
Developer, Cybrilla & Fintech Primitives
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