- 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.
129 lines
3.0 KiB
Ruby
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
|