Files
redux-scraper/app/helpers/good_job_helper.rb
Dylan Knutson 4af584fffd Add GoodJob logging enhancements and custom styles
- Introduced a new `good_job_custom.css` file for custom styling of GoodJob logs.
- Added a new `pixiv.png` icon for domain-specific logging in the `e621` posts helper.
- Enhanced the `GoodJobHelper` module to parse ANSI escape codes for better log formatting.
- Implemented a new `GoodJobExecutionLogLinesCollection` model to store log lines associated with job executions.
- Updated views to display job execution details and logs with improved formatting and styling.
- Refactored `ColorLogger` to support log line accumulation for better log management.

These changes aim to improve the logging experience and visual representation of job execution details in the GoodJob dashboard.
2025-01-03 05:58:22 +00:00

129 lines
3.0 KiB
Ruby

# typed: strict
# frozen_string_literal: true
module GoodJobHelper
extend T::Sig
extend T::Helpers
extend self
class AnsiSegment < T::Struct
const :text, String
const :class_names, T::Array[String]
end
# ANSI escape code pattern
ANSI_PATTERN = /\e\[([0-9;]*)m/
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
sig { params(text: String).returns(T::Array[AnsiSegment]) }
def parse_ansi(text)
segments = []
current_classes = T::Array[String].new
# Split the text into parts based on ANSI codes
parts = text.split(ANSI_PATTERN)
# Process each part and its corresponding ANSI codes
parts.each_with_index do |part, index|
if index.even?
# This is text content
segments << AnsiSegment.new(
text: part,
class_names: current_classes.dup,
)
else
# This is an ANSI code
codes = part.split(";").map(&:to_i)
if codes == [0]
current_classes.clear
else
codes.each do |code|
class_name = ansi_code_to_class(code)
current_classes << class_name if class_name
end
end
end
end
# go through segments and detect UUIDs, splitting the segment at the uuid
# and adding them to the segments array. Should result in a <before>, <uuid>,
# <after> tuple.
segments.flat_map do |segment|
if segment.text.match?(UUID_REGEX)
idx = segment.text.index(UUID_REGEX)
[
AnsiSegment.new(
text: segment.text[0...idx],
class_names: segment.class_names,
),
AnsiSegment.new(
text: segment.text[idx...idx + 36],
class_names: ["log-uuid"],
),
AnsiSegment.new(
text: segment.text[idx + 36..],
class_names: segment.class_names,
),
]
else
[segment]
end
end
end
sig { params(job: GoodJob::Job).returns(T::Hash[String, T.untyped]) }
def arguments_for_job(job)
deserialized =
T.cast(
ActiveJob::Arguments.deserialize(job.serialized_params).to_h,
T::Hash[String, T.untyped],
)
args = deserialized["arguments"].first
args.sort_by { |key, _| key.to_s }.to_h
end
private
sig { params(code: Integer).returns(T.nilable(String)) }
def ansi_code_to_class(code)
case code
when 1
"ansi-bold"
when 30
"ansi-black"
when 31
"ansi-red"
when 32
"ansi-green"
when 33
"ansi-yellow"
when 34
"ansi-blue"
when 35
"ansi-magenta"
when 36
"ansi-cyan"
when 37
"ansi-white"
when 90
"ansi-bright-black"
when 91
"ansi-bright-red"
when 92
"ansi-bright-green"
when 93
"ansi-bright-yellow"
when 94
"ansi-bright-blue"
when 95
"ansi-bright-magenta"
when 96
"ansi-bright-cyan"
when 97
"ansi-bright-white"
else
nil
end
end
end