mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 23:25:00 +00:00
feat(api): expose rule run history (#1646)
* feat(api): expose rule run history * fix(api): address rule run review * fix(api): complete rule run review * test(api): cover unauthenticated rule run show * test(api): align rule run api key helper * Small Sonnet nit-pick --------- Co-authored-by: Juan José Mata <jjmata@jjmata.com>
This commit is contained in:
@@ -216,6 +216,28 @@ class Api::V1::BaseController < ApplicationController
|
||||
value.to_s.match?(UUID_PATTERN)
|
||||
end
|
||||
|
||||
def safe_page_param
|
||||
page = params[:page].to_i
|
||||
page > 0 ? page : 1
|
||||
end
|
||||
|
||||
def safe_per_page_param
|
||||
per_page = params[:per_page].to_i
|
||||
case per_page
|
||||
when 1..100 then per_page
|
||||
when (101..) then 100
|
||||
else 25
|
||||
end
|
||||
end
|
||||
|
||||
def render_validation_error(message)
|
||||
render_json({
|
||||
error: "validation_failed",
|
||||
message: message,
|
||||
errors: [ message ]
|
||||
}, status: :unprocessable_entity)
|
||||
end
|
||||
|
||||
# Error handlers
|
||||
def handle_not_found(exception)
|
||||
Rails.logger.warn "API Record Not Found: #{exception.message}"
|
||||
|
||||
82
app/controllers/api/v1/rule_runs_controller.rb
Normal file
82
app/controllers/api/v1/rule_runs_controller.rb
Normal file
@@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::RuleRunsController < Api::V1::BaseController
|
||||
include Pagy::Backend
|
||||
|
||||
STATUSES = %w[pending success failed].freeze
|
||||
EXECUTION_TYPES = %w[manual scheduled].freeze
|
||||
InvalidFilterError = Class.new(StandardError)
|
||||
|
||||
before_action :ensure_read_scope
|
||||
before_action :set_rule_run, only: :show
|
||||
|
||||
def index
|
||||
rule_runs_query = apply_filters(rule_runs_scope).recent
|
||||
@per_page = safe_per_page_param
|
||||
|
||||
@pagy, @rule_runs = pagy(
|
||||
rule_runs_query,
|
||||
page: safe_page_param,
|
||||
limit: @per_page
|
||||
)
|
||||
|
||||
render :index
|
||||
rescue InvalidFilterError => e
|
||||
render_validation_error(e.message)
|
||||
end
|
||||
|
||||
def show
|
||||
render :show
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_rule_run
|
||||
raise ActiveRecord::RecordNotFound, "Rule run not found" unless valid_uuid?(params[:id])
|
||||
|
||||
@rule_run = rule_runs_scope.find(params[:id])
|
||||
end
|
||||
|
||||
def ensure_read_scope
|
||||
authorize_scope!(:read)
|
||||
end
|
||||
|
||||
def rule_runs_scope
|
||||
RuleRun
|
||||
.joins(:rule)
|
||||
.where(rules: { family_id: Current.family.id })
|
||||
.includes(:rule)
|
||||
end
|
||||
|
||||
def apply_filters(query)
|
||||
if params[:rule_id].present?
|
||||
raise InvalidFilterError, "rule_id must be a valid UUID" unless valid_uuid?(params[:rule_id])
|
||||
|
||||
query = query.where(rule_id: params[:rule_id])
|
||||
end
|
||||
|
||||
if params[:status].present?
|
||||
raise InvalidFilterError, "status must be one of: #{STATUSES.join(', ')}" unless STATUSES.include?(params[:status])
|
||||
|
||||
query = query.where(status: params[:status])
|
||||
end
|
||||
|
||||
if params[:execution_type].present?
|
||||
unless EXECUTION_TYPES.include?(params[:execution_type])
|
||||
raise InvalidFilterError, "execution_type must be one of: #{EXECUTION_TYPES.join(', ')}"
|
||||
end
|
||||
|
||||
query = query.where(execution_type: params[:execution_type])
|
||||
end
|
||||
|
||||
query = query.where("rule_runs.executed_at >= ?", parse_time_param(:start_executed_at)) if params[:start_executed_at].present?
|
||||
query = query.where("rule_runs.executed_at <= ?", parse_time_param(:end_executed_at)) if params[:end_executed_at].present?
|
||||
query
|
||||
end
|
||||
|
||||
def parse_time_param(key)
|
||||
Time.iso8601(params[key].to_s)
|
||||
rescue ArgumentError
|
||||
raise InvalidFilterError, "#{key} must be an ISO 8601 timestamp"
|
||||
end
|
||||
end
|
||||
@@ -55,29 +55,11 @@ class Api::V1::RulesController < Api::V1::BaseController
|
||||
authorize_scope!(:read)
|
||||
end
|
||||
|
||||
def safe_page_param
|
||||
page = params[:page].to_i
|
||||
page > 0 ? page : 1
|
||||
end
|
||||
|
||||
def safe_per_page_param
|
||||
per_page = params[:per_page].to_i
|
||||
case per_page
|
||||
when 1..100
|
||||
per_page
|
||||
else
|
||||
25
|
||||
end
|
||||
end
|
||||
|
||||
def parse_boolean_filter(value)
|
||||
normalized = value.to_s.downcase
|
||||
return BOOLEAN_FILTERS[normalized] if BOOLEAN_FILTERS.key?(normalized)
|
||||
|
||||
render json: {
|
||||
error: "validation_failed",
|
||||
message: "active must be one of: true, false, 1, 0"
|
||||
}, status: :unprocessable_entity
|
||||
render_validation_error("active must be one of: true, false, 1, 0")
|
||||
nil
|
||||
end
|
||||
|
||||
@@ -86,9 +68,6 @@ class Api::V1::RulesController < Api::V1::BaseController
|
||||
end
|
||||
|
||||
def render_invalid_resource_type_filter
|
||||
render json: {
|
||||
error: "validation_failed",
|
||||
message: "resource_type must be one of: #{RESOURCE_TYPES.join(", ")}"
|
||||
}, status: :unprocessable_entity
|
||||
render_validation_error("resource_type must be one of: #{RESOURCE_TYPES.join(", ")}")
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user