Rails app blueprint

Solid Queue

Add Avo configuration files to manage your Solid Queue jobs

Avo is just awesome. We migrated our custom admin dashboard within a few weeks...
It saved us hundreds of hours of development work and we could focus on our main business!
We love the quick (and awesome) support and the weekly release cycle with great updates.
A real game changer! :)
Jonas Reif
Jonas Reif
Co-Founder, Upspeak.de
Run the following command to quickly apply this app blueprint

If you think this blueprint needs changes please submit a PR here.

Full blueprint

# Blueprint info:
#   Link: https://avohq.io/blueprints/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