DSL for scans with intervals
This commit is contained in:
BIN
app/assets/images/domain-icons/boosty.png
Normal file
BIN
app/assets/images/domain-icons/boosty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
1
app/assets/images/domain-icons/sorbet/rbi/dsl/.gitattributes
vendored
Normal file
1
app/assets/images/domain-icons/sorbet/rbi/dsl/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
**/*.rbi linguist-generated=true
|
||||
23
app/assets/images/domain-icons/sorbet/rbi/dsl/active_support/callbacks.rbi
generated
Normal file
23
app/assets/images/domain-icons/sorbet/rbi/dsl/active_support/callbacks.rbi
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `ActiveSupport::Callbacks`.
|
||||
# Please instead update this file by running `bin/tapioca dsl ActiveSupport::Callbacks`.
|
||||
|
||||
|
||||
module ActiveSupport::Callbacks
|
||||
include GeneratedInstanceMethods
|
||||
|
||||
mixes_in_class_methods GeneratedClassMethods
|
||||
|
||||
module GeneratedClassMethods
|
||||
def __callbacks; end
|
||||
def __callbacks=(value); end
|
||||
def __callbacks?; end
|
||||
end
|
||||
|
||||
module GeneratedInstanceMethods
|
||||
def __callbacks; end
|
||||
def __callbacks?; end
|
||||
end
|
||||
end
|
||||
@@ -83,15 +83,15 @@ class Domain::Fa::ApiController < ApplicationController
|
||||
object_url: request.base_url + helpers.domain_user_path(user),
|
||||
page_scan: {
|
||||
last_at: time_ago_or_never(user.scanned_page_at),
|
||||
due_for_scan: user.due_for_page_scan?,
|
||||
due_for_scan: user.page_scan.due?,
|
||||
},
|
||||
gallery_scan: {
|
||||
last_at: time_ago_or_never(user.scanned_gallery_at),
|
||||
due_for_scan: user.due_for_gallery_scan?,
|
||||
last_at: time_ago_or_never(user.gallery_scan.at),
|
||||
due_for_scan: user.gallery_scan.due?,
|
||||
},
|
||||
favs_scan: {
|
||||
last_at: time_ago_or_never(user.scanned_favs_at),
|
||||
due_for_scan: user.due_for_favs_scan?,
|
||||
last_at: time_ago_or_never(user.favs_scan.at),
|
||||
due_for_scan: user.favs_scan.due?,
|
||||
},
|
||||
}
|
||||
else
|
||||
@@ -348,12 +348,12 @@ class Domain::Fa::ApiController < ApplicationController
|
||||
)
|
||||
profile_thumb_url = parser.user_page.profile_thumb_url
|
||||
else
|
||||
if user.due_for_follows_scan?
|
||||
if user.follows_scan.due?
|
||||
Domain::Fa::Job::UserFollowsJob.set(
|
||||
{ priority: -20 },
|
||||
).perform_later({ user: user })
|
||||
end
|
||||
if user.due_for_page_scan?
|
||||
if user.page_scan.due?
|
||||
Domain::Fa::Job::UserPageJob.set({ priority: -20 }).perform_later(
|
||||
{ user: user },
|
||||
)
|
||||
|
||||
@@ -33,6 +33,8 @@ module Domain::DomainsHelper
|
||||
gumroad.com
|
||||
bigcartel.com
|
||||
furaffinity.net
|
||||
boosty.to
|
||||
hipolink.me
|
||||
].freeze
|
||||
|
||||
DOMAIN_TO_ICON_PATH =
|
||||
@@ -56,6 +58,7 @@ module Domain::DomainsHelper
|
||||
"redbubble.com" => "redbubble.png",
|
||||
"spreadshirt.de" => "spreadshirt.png",
|
||||
"spreadshirt.com" => "spreadshirt.png",
|
||||
"boosty.to" => "boosty.png",
|
||||
}.freeze,
|
||||
T::Hash[String, String],
|
||||
)
|
||||
|
||||
@@ -90,44 +90,93 @@ module Domain::UsersHelper
|
||||
"#{domain_user_path(user)}/favorites"
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User).returns(T::Array[[String, Integer]]) }
|
||||
class StatRow < T::ImmutableStruct
|
||||
const :name, String
|
||||
const :value,
|
||||
T.nilable(
|
||||
T.any(String, Integer, HasTimestampsWithDueAt::TimestampScanInfo),
|
||||
)
|
||||
const :link_to, T.nilable(String)
|
||||
const :fa_icon_class, T.nilable(String)
|
||||
const :hover_title, T.nilable(String)
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User).returns(T::Array[StatRow]) }
|
||||
def stat_rows_for_user(user)
|
||||
rows = []
|
||||
rows << ["Favorites", user.user_post_favs.count] if user.has_faved_posts?
|
||||
rows = T.let([], T::Array[StatRow])
|
||||
if user.has_faved_posts?
|
||||
rows << StatRow.new(name: "Favorites", value: user.user_post_favs.count)
|
||||
end
|
||||
if user.has_followed_users?
|
||||
rows << ["Following", user.user_user_follows_from.count]
|
||||
rows << StatRow.new(
|
||||
name: "Following",
|
||||
value: user.user_user_follows_from.count,
|
||||
)
|
||||
end
|
||||
if user.has_followed_by_users?
|
||||
rows << ["Followed by", user.user_user_follows_to.count]
|
||||
rows << StatRow.new(
|
||||
name: "Followed by",
|
||||
value: user.user_user_follows_to.count,
|
||||
)
|
||||
end
|
||||
|
||||
can_view_timestamps = policy(user).view_page_scanned_at_timestamps?
|
||||
can_view_log_entries = policy(user).view_log_entries?
|
||||
icon_for =
|
||||
Kernel.proc do |due_for_scan|
|
||||
due_for_scan ? "fa-hourglass-half" : "fa-check"
|
||||
end
|
||||
|
||||
if user.is_a?(Domain::User::FaUser) && can_view_timestamps
|
||||
rows << ["Favorites", time_ago_or_never(user.scanned_favs_at)]
|
||||
rows << ["Gallery scanned", time_ago_or_never(user.scanned_gallery_at)]
|
||||
rows << StatRow.new(
|
||||
name: "Favs scanned",
|
||||
value: user.favs_scan,
|
||||
fa_icon_class: icon_for.call(user.favs_scan.due?),
|
||||
hover_title: user.favs_scan.interval.inspect,
|
||||
)
|
||||
rows << StatRow.new(
|
||||
name: "Gallery scanned",
|
||||
value: user.gallery_scan,
|
||||
fa_icon_class: icon_for.call(user.gallery_scan.due?),
|
||||
hover_title: user.gallery_scan.interval.inspect,
|
||||
)
|
||||
rows << StatRow.new(
|
||||
name: "Follows scanned",
|
||||
value: user.follows_scan,
|
||||
fa_icon_class: icon_for.call(user.follows_scan.due?),
|
||||
hover_title: user.follows_scan.interval.inspect,
|
||||
)
|
||||
if can_view_log_entries && hle = user.guess_last_user_page_log_entry
|
||||
rows << [
|
||||
"Page scanned",
|
||||
raw(
|
||||
link_to(
|
||||
time_ago_or_never(user.scanned_page_at),
|
||||
log_entry_path(hle),
|
||||
class: "blue-link",
|
||||
),
|
||||
),
|
||||
]
|
||||
rows << StatRow.new(
|
||||
name: "Page scanned",
|
||||
value: user.page_scan,
|
||||
link_to: log_entry_path(hle),
|
||||
fa_icon_class: icon_for.call(user.page_scan.due?),
|
||||
hover_title: user.page_scan.interval.inspect,
|
||||
)
|
||||
else
|
||||
rows << ["Page scanned", time_ago_or_never(user.scanned_page_at)]
|
||||
rows << StatRow.new(
|
||||
name: "Page scanned",
|
||||
value: user.page_scan,
|
||||
fa_icon_class: icon_for.call(user.page_scan.due?),
|
||||
hover_title: user.page_scan.interval.inspect,
|
||||
)
|
||||
end
|
||||
elsif user.is_a?(Domain::User::E621User) && can_view_timestamps
|
||||
if user.favs_are_hidden
|
||||
rows << ["Favorites hidden", "yes"]
|
||||
rows << StatRow.new(name: "Favorites hidden", value: "yes")
|
||||
else
|
||||
rows << ["Server favorites", user.num_other_favs_cached]
|
||||
rows << StatRow.new(
|
||||
name: "Server favorites",
|
||||
value: user.num_other_favs_cached,
|
||||
)
|
||||
end
|
||||
rows << ["Favorites scanned", time_ago_or_never(user.scanned_favs_at)]
|
||||
rows << StatRow.new(
|
||||
name: "Favorites scanned",
|
||||
value: user.favs_scan,
|
||||
fa_icon_class: icon_for.call(user.favs_scan.due?),
|
||||
hover_title: user.favs_scan.interval.inspect,
|
||||
)
|
||||
end
|
||||
|
||||
rows
|
||||
|
||||
@@ -86,15 +86,15 @@ class Domain::Fa::Job::Base < Scraper::JobBase
|
||||
|
||||
sig { params(user: Domain::User::FaUser).returns(T::Boolean) }
|
||||
def user_due_for_favs_scan?(user)
|
||||
unless user.due_for_favs_scan?
|
||||
unless user.favs_scan.due?
|
||||
if force_scan?
|
||||
logger.warn(
|
||||
"scanned favs #{DateHelper.time_ago_in_words(user.scanned_favs_at).bold} ago - force scanning",
|
||||
"scanned favs #{user.favs_scan.ago_in_words.bold} ago - force scanning",
|
||||
)
|
||||
return true
|
||||
else
|
||||
logger.warn(
|
||||
"scanned favs #{DateHelper.time_ago_in_words(user.scanned_favs_at).bold} ago - skipping",
|
||||
"scanned favs #{user.favs_scan.ago_in_words.bold} ago - skipping",
|
||||
)
|
||||
return false
|
||||
end
|
||||
@@ -232,47 +232,40 @@ class Domain::Fa::Job::Base < Scraper::JobBase
|
||||
{ url_name: user.url_name }
|
||||
end
|
||||
|
||||
if user.due_for_page_scan? &&
|
||||
defer_job(Domain::Fa::Job::UserPageJob, args)
|
||||
if user.page_scan.due? && defer_job(Domain::Fa::Job::UserPageJob, args)
|
||||
logger.info(
|
||||
format_tags(
|
||||
"enqueue user page job",
|
||||
make_tag("last scanned", time_ago_in_words(user.scanned_page_at)),
|
||||
make_tag("last page scan", user.page_scan.ago_in_words),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
if user.due_for_gallery_scan? &&
|
||||
if user.gallery_scan.due? &&
|
||||
defer_job(Domain::Fa::Job::UserGalleryJob, args)
|
||||
logger.info(
|
||||
format_tags(
|
||||
"enqueue user gallery job",
|
||||
make_tag(
|
||||
"last scanned",
|
||||
time_ago_in_words(user.scanned_gallery_at),
|
||||
),
|
||||
make_tag("last gallery scan", user.gallery_scan.ago_in_words),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
if user.due_for_follows_scan? &&
|
||||
if user.follows_scan.due? &&
|
||||
defer_job(Domain::Fa::Job::UserFollowsJob, args)
|
||||
logger.info(
|
||||
format_tags(
|
||||
"enqueue user follows job",
|
||||
make_tag(
|
||||
"last scanned",
|
||||
time_ago_in_words(user.scanned_follows_at),
|
||||
),
|
||||
make_tag("last follows scan", user.follows_scan.ago_in_words),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
if user.due_for_favs_scan? && defer_job(Domain::Fa::Job::FavsJob, args)
|
||||
if user.favs_scan.due? && defer_job(Domain::Fa::Job::FavsJob, args)
|
||||
logger.info(
|
||||
format_tags(
|
||||
"enqueue user favs job",
|
||||
make_tag("last scanned", time_ago_in_words(user.scanned_favs_at)),
|
||||
make_tag("last favs scan", user.favs_scan.ago_in_words),
|
||||
),
|
||||
)
|
||||
end
|
||||
|
||||
@@ -27,10 +27,8 @@ class Domain::Fa::Job::UserFollowsJob < Domain::Fa::Job::Base
|
||||
def perform(args)
|
||||
user = user_from_args!
|
||||
|
||||
if !user.due_for_follows_scan? && !force_scan?
|
||||
logger.warn(
|
||||
"scanned #{time_ago_in_words(user.scanned_follows_at)}, skipping",
|
||||
)
|
||||
if !user.follows_scan.due? && !force_scan?
|
||||
logger.warn("scanned #{user.follows_scan.ago_in_words}, skipping")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -37,17 +37,10 @@ class Domain::Fa::Job::UserGalleryJob < Domain::Fa::Job::Base
|
||||
# buggy (sentinal) user
|
||||
return if user.id == 117_552 && user.url_name == "click here"
|
||||
|
||||
@go_until_end = user.scanned_gallery_at.nil?
|
||||
if (num_submissions = user.num_submissions) &&
|
||||
(scanned_page_at = user.scanned_page_at) &&
|
||||
(scanned_page_at > 3.days.ago)
|
||||
@max_page_number = [@max_page_number, (num_submissions * 72) + 3].max
|
||||
end
|
||||
@go_until_end = user.gallery_scan.at.nil?
|
||||
|
||||
if !user.due_for_gallery_scan? && !force_scan?
|
||||
logger.warn(
|
||||
"gallery scanned #{time_ago_in_words(user.scanned_page_at)}, skipping",
|
||||
)
|
||||
if !user.gallery_scan.due? && !force_scan?
|
||||
logger.warn("gallery scanned #{user.gallery_scan.ago_in_words}, skipping")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -19,13 +19,10 @@ module Domain::Fa::Job
|
||||
# - follows / following: look at the 'watchers' / 'watching' section,
|
||||
# and add new follows.
|
||||
|
||||
if !user.due_for_incremental_scan? && !force_scan?
|
||||
if !user.incremental_scan.due? && !force_scan?
|
||||
logger.warn(
|
||||
format_tags(
|
||||
make_tag(
|
||||
"incremental scanned",
|
||||
time_ago_in_words(user.scanned_incremental_at),
|
||||
),
|
||||
make_tag("incremental scanned", user.incremental_scan.ago_in_words),
|
||||
"scanned recently, skipping",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -14,10 +14,8 @@ class Domain::Fa::Job::UserPageJob < Domain::Fa::Job::Base
|
||||
# buggy (sentinal) user
|
||||
return if user.id == 117_552 && user.url_name == "click here"
|
||||
|
||||
if !user.due_for_page_scan? && !force_scan?
|
||||
logger.warn(
|
||||
"scanned #{time_ago_in_words(user.scanned_page_at)}, skipping",
|
||||
)
|
||||
if !user.page_scan.due? && !force_scan?
|
||||
logger.warn("scanned #{user.page_scan.ago_in_words}, skipping")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -44,26 +44,26 @@ class Domain::Fa::UserEnqueuer
|
||||
rows.each do |user|
|
||||
types = []
|
||||
if user.state == "ok"
|
||||
if user.due_for_favs_scan? || user.due_for_page_scan? ||
|
||||
user.due_for_follows_scan?
|
||||
if user.favs_scan.due? || user.page_scan.due? ||
|
||||
user.follows_scan.due?
|
||||
Domain::Fa::Job::UserIncrementalJob.perform_later({ user: user })
|
||||
types << "incremental"
|
||||
end
|
||||
|
||||
if user.due_for_favs_scan?
|
||||
if user.favs_scan.due?
|
||||
Domain::Fa::Job::FavsJob.perform_later({ user: user })
|
||||
types << "favs"
|
||||
end
|
||||
if user.due_for_page_scan?
|
||||
if user.page_scan.due?
|
||||
Domain::Fa::Job::UserPageJob.perform_later({ user: user })
|
||||
types << "page"
|
||||
end
|
||||
|
||||
if user.due_for_gallery_scan?
|
||||
if user.gallery_scan.due?
|
||||
Domain::Fa::Job::UserGalleryJob.perform_later({ user: user })
|
||||
types << "gallery"
|
||||
end
|
||||
if user.due_for_follows_scan?
|
||||
if user.follows_scan.due?
|
||||
Domain::Fa::Job::UserFollowsJob.perform_later({ user: user })
|
||||
types << "follows"
|
||||
end
|
||||
|
||||
62
app/models/concerns/has_timestamps_with_due_at.rb
Normal file
62
app/models/concerns/has_timestamps_with_due_at.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HasTimestampsWithDueAt
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
extend ActiveSupport::Concern
|
||||
requires_ancestor { ReduxApplicationRecord }
|
||||
|
||||
class TimeHelper
|
||||
extend ActionView::Helpers::DateHelper
|
||||
end
|
||||
|
||||
class TimestampScanInfo < T::ImmutableStruct
|
||||
extend T::Sig
|
||||
|
||||
const :at, T.nilable(ActiveSupport::TimeWithZone)
|
||||
const :interval, ActiveSupport::Duration
|
||||
|
||||
sig { returns(String) }
|
||||
def ago_in_words
|
||||
at = self.at
|
||||
at ? TimeHelper.time_ago_in_words(at) + " ago" : "never"
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due?
|
||||
at = self.at
|
||||
at ? at < interval.ago : true
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
included do
|
||||
T.unsafe(self).class_attribute :due_at_timestamp_fields, default: []
|
||||
|
||||
sig(:final) do
|
||||
params(field: Symbol, interval: ActiveSupport::Duration).void
|
||||
end
|
||||
def self.attr_json_due_timestamp(field, interval)
|
||||
field_pattern = /scanned_(.*)_at/
|
||||
field_name = field_pattern.match(field.to_s)&.[](1)&.to_sym
|
||||
raise "Invalid field name: #{field}" if field_name.nil?
|
||||
|
||||
# define the attribute
|
||||
T.unsafe(self).attr_json field, :datetime
|
||||
|
||||
T
|
||||
.unsafe(self)
|
||||
.define_method(:"#{field_name}_scan") do
|
||||
T.bind(self, HasTimestampsWithDueAt)
|
||||
TimestampScanInfo.new(at: read_attribute(field), interval:)
|
||||
end
|
||||
|
||||
T.unsafe(self).due_at_timestamp_fields =
|
||||
(T.unsafe(self).due_at_timestamp_fields || {}).merge(
|
||||
{ field_name => interval },
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,7 @@ class Domain::User < ReduxApplicationRecord
|
||||
include HasCompositeToParam
|
||||
include HasViewPrefix
|
||||
include HasDescriptionHtmlForView
|
||||
include HasTimestampsWithDueAt
|
||||
|
||||
self.table_name = "domain_users"
|
||||
abstract!
|
||||
@@ -12,7 +13,8 @@ class Domain::User < ReduxApplicationRecord
|
||||
class_attribute :class_has_created_posts,
|
||||
:class_has_faved_posts,
|
||||
:class_has_followed_users,
|
||||
:class_has_followed_by_users
|
||||
:class_has_followed_by_users,
|
||||
:due_at_timestamp_fields
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def has_created_posts?
|
||||
|
||||
@@ -5,7 +5,7 @@ class Domain::User::E621User < Domain::User
|
||||
attr_json :favs_are_hidden, :boolean
|
||||
attr_json :num_other_favs_cached, :integer
|
||||
attr_json :scanned_favs_status, :string
|
||||
attr_json :scanned_favs_at, :datetime
|
||||
attr_json_due_timestamp :scanned_favs_at, 1.month
|
||||
attr_json :registered_at, :datetime
|
||||
|
||||
enum :scanned_favs_status, { ok: "ok", error: "error" }, prefix: :scanned_favs
|
||||
|
||||
@@ -14,11 +14,11 @@ class Domain::User::FaUser < Domain::User
|
||||
attr_json :num_comments_given, :integer
|
||||
attr_json :num_journals, :integer
|
||||
attr_json :num_favorites, :integer
|
||||
attr_json :scanned_gallery_at, :datetime
|
||||
attr_json :scanned_page_at, :datetime
|
||||
attr_json :scanned_follows_at, :datetime
|
||||
attr_json :scanned_favs_at, :datetime
|
||||
attr_json :scanned_incremental_at, :datetime
|
||||
attr_json_due_timestamp :scanned_gallery_at, 1.year
|
||||
attr_json_due_timestamp :scanned_page_at, 1.month
|
||||
attr_json_due_timestamp :scanned_follows_at, 1.month
|
||||
attr_json_due_timestamp :scanned_favs_at, 1.month
|
||||
attr_json_due_timestamp :scanned_incremental_at, 1.month
|
||||
attr_json :registered_at, :datetime
|
||||
attr_json :migrated_followed_users_at, :datetime
|
||||
attr_json :last_user_page_id, :integer
|
||||
@@ -87,31 +87,6 @@ class Domain::User::FaUser < Domain::User
|
||||
[name, url_name, full_name].compact
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_favs_scan?
|
||||
due_for_scan?(scanned_favs_at, 1.month.ago)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_follows_scan?
|
||||
due_for_scan?(scanned_follows_at, 1.month.ago)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_page_scan?
|
||||
due_for_scan?(scanned_page_at, 1.month.ago)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_gallery_scan?
|
||||
due_for_scan?(scanned_gallery_at, 1.year.ago)
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def due_for_incremental_scan?
|
||||
due_for_scan?(scanned_incremental_at, 1.month.ago)
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
submission_parser:
|
||||
@@ -182,16 +157,4 @@ class Domain::User::FaUser < Domain::User
|
||||
def name_for_view
|
||||
name || url_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig do
|
||||
params(
|
||||
at: T.nilable(ActiveSupport::TimeWithZone),
|
||||
ago: ActiveSupport::TimeWithZone,
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def due_for_scan?(at, ago)
|
||||
at ? at < ago : true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
<section class="sky-section animated-shadow-sky divide-y">
|
||||
<h2 class="section-header">User Stats</h2>
|
||||
<% stat_rows_for_user(user).each do |value_label, value| %>
|
||||
<% stat_rows_for_user(user).each do |stat_row| %>
|
||||
<% label = stat_row.name %>
|
||||
<% value = stat_row.value %>
|
||||
<% fa_icon_class = stat_row.fa_icon_class %>
|
||||
<div class="flex items-center px-4 py-2">
|
||||
<span class="grow text-slate-900"><%= value_label %></span>
|
||||
<span class="text-slate-500">
|
||||
<%= value.is_a?(Integer) ? number_with_delimiter(value, delimiter: ",") : value %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<span class="grow text-slate-900"><%= label %></span>
|
||||
<span class="text-slate-500 relative group">
|
||||
<% value_str = case value %>
|
||||
<% when Integer %>
|
||||
<% number_with_delimiter(value, delimiter: ",") %>
|
||||
<% when HasTimestampsWithDueAt::TimestampScanInfo %>
|
||||
<% value.ago_in_words %>
|
||||
<% else %>
|
||||
<% value %>
|
||||
<% end %>
|
||||
<% if stat_row.link_to %>
|
||||
<%= link_to value_str, stat_row.link_to, class: "blue-link" %>
|
||||
<% else %>
|
||||
<%= value_str %>
|
||||
<% end %>
|
||||
<% if fa_icon_class %>
|
||||
<i class="fa-solid <%= fa_icon_class %>"></i>
|
||||
<% end %>
|
||||
<% if stat_row.hover_title %>
|
||||
<div class="absolute hidden group-hover:block bg-slate-800 text-white text-sm rounded px-2 py-1 top-1/2 -translate-y-1/2 right-full mr-2 whitespace-nowrap">
|
||||
<%= stat_row.hover_title %>
|
||||
</div>
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
11
sorbet/rbi/dsl/application_controller.rbi
generated
11
sorbet/rbi/dsl/application_controller.rbi
generated
@@ -29,16 +29,19 @@ class ApplicationController
|
||||
include ::Webpacker::Helper
|
||||
include ::ActionController::Base::HelperMethods
|
||||
include ::ApplicationHelper
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::DescriptionsHelper
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::ModelHelper
|
||||
include ::Domain::PaginationHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::DomainSourceHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
|
||||
11
sorbet/rbi/dsl/devise_controller.rbi
generated
11
sorbet/rbi/dsl/devise_controller.rbi
generated
@@ -26,16 +26,19 @@ class DeviseController
|
||||
include ::Webpacker::Helper
|
||||
include ::ActionController::Base::HelperMethods
|
||||
include ::ApplicationHelper
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::DescriptionsHelper
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::ModelHelper
|
||||
include ::Domain::PaginationHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::DomainSourceHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
|
||||
3
sorbet/rbi/dsl/domain/user.rbi
generated
3
sorbet/rbi/dsl/domain/user.rbi
generated
@@ -38,6 +38,9 @@ class Domain::User
|
||||
end
|
||||
def attr_json_config(default_container_attribute: nil, bad_cast: nil, unknown_key: nil); end
|
||||
|
||||
sig { params(field: Symbol, interval: ActiveSupport::Duration).void }
|
||||
def attr_json_due_timestamp(field, interval); end
|
||||
|
||||
sig { returns(T::Array[Symbol]) }
|
||||
def attr_json_registry; end
|
||||
|
||||
|
||||
6
sorbet/rbi/dsl/domain/user/e621_user.rbi
generated
6
sorbet/rbi/dsl/domain/user/e621_user.rbi
generated
@@ -12,6 +12,9 @@ class Domain::User::E621User
|
||||
extend CommonRelationMethods
|
||||
extend GeneratedRelationMethods
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def favs_scan; end
|
||||
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
@@ -39,6 +42,9 @@ class Domain::User::E621User
|
||||
end
|
||||
def attr_json_config(default_container_attribute: nil, bad_cast: nil, unknown_key: nil); end
|
||||
|
||||
sig { params(field: Symbol, interval: ActiveSupport::Duration).void }
|
||||
def attr_json_due_timestamp(field, interval); end
|
||||
|
||||
sig { returns(T::Array[Symbol]) }
|
||||
def attr_json_registry; end
|
||||
|
||||
|
||||
18
sorbet/rbi/dsl/domain/user/fa_user.rbi
generated
18
sorbet/rbi/dsl/domain/user/fa_user.rbi
generated
@@ -12,9 +12,24 @@ class Domain::User::FaUser
|
||||
extend CommonRelationMethods
|
||||
extend GeneratedRelationMethods
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def favs_scan; end
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def follows_scan; end
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def gallery_scan; end
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def incremental_scan; end
|
||||
|
||||
sig { returns(ColorLogger) }
|
||||
def logger; end
|
||||
|
||||
sig { returns(HasTimestampsWithDueAt::TimestampScanInfo) }
|
||||
def page_scan; end
|
||||
|
||||
private
|
||||
|
||||
sig { returns(NilClass) }
|
||||
@@ -39,6 +54,9 @@ class Domain::User::FaUser
|
||||
end
|
||||
def attr_json_config(default_container_attribute: nil, bad_cast: nil, unknown_key: nil); end
|
||||
|
||||
sig { params(field: Symbol, interval: ActiveSupport::Duration).void }
|
||||
def attr_json_due_timestamp(field, interval); end
|
||||
|
||||
sig { returns(T::Array[Symbol]) }
|
||||
def attr_json_registry; end
|
||||
|
||||
|
||||
3
sorbet/rbi/dsl/domain/user/inkbunny_user.rbi
generated
3
sorbet/rbi/dsl/domain/user/inkbunny_user.rbi
generated
@@ -38,6 +38,9 @@ class Domain::User::InkbunnyUser
|
||||
end
|
||||
def attr_json_config(default_container_attribute: nil, bad_cast: nil, unknown_key: nil); end
|
||||
|
||||
sig { params(field: Symbol, interval: ActiveSupport::Duration).void }
|
||||
def attr_json_due_timestamp(field, interval); end
|
||||
|
||||
sig { returns(T::Array[Symbol]) }
|
||||
def attr_json_registry; end
|
||||
|
||||
|
||||
5
sorbet/rbi/dsl/generated_path_helpers_module.rbi
generated
5
sorbet/rbi/dsl/generated_path_helpers_module.rbi
generated
@@ -84,6 +84,9 @@ module GeneratedPathHelpersModule
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def favorites_user_posts_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def furecs_user_script_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def global_state_path(*args); end
|
||||
|
||||
@@ -133,7 +136,7 @@ module GeneratedPathHelpersModule
|
||||
def pg_hero_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def pool_path(*args); end
|
||||
def post_group_posts_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def post_path(*args); end
|
||||
|
||||
5
sorbet/rbi/dsl/generated_url_helpers_module.rbi
generated
5
sorbet/rbi/dsl/generated_url_helpers_module.rbi
generated
@@ -84,6 +84,9 @@ module GeneratedUrlHelpersModule
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def favorites_user_posts_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def furecs_user_script_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def global_state_url(*args); end
|
||||
|
||||
@@ -133,7 +136,7 @@ module GeneratedUrlHelpersModule
|
||||
def pg_hero_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def pool_url(*args); end
|
||||
def post_group_posts_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def post_url(*args); end
|
||||
|
||||
2
sorbet/rbi/dsl/good_job/jobs_controller.rbi
generated
2
sorbet/rbi/dsl/good_job/jobs_controller.rbi
generated
@@ -28,6 +28,8 @@ class GoodJob::JobsController
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::GoodJobHelper
|
||||
|
||||
24
sorbet/rbi/dsl/has_timestamps_with_due_at.rbi
generated
Normal file
24
sorbet/rbi/dsl/has_timestamps_with_due_at.rbi
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for dynamic methods in `HasTimestampsWithDueAt`.
|
||||
# Please instead update this file by running `bin/tapioca dsl HasTimestampsWithDueAt`.
|
||||
|
||||
|
||||
module HasTimestampsWithDueAt
|
||||
include GeneratedInstanceMethods
|
||||
|
||||
mixes_in_class_methods GeneratedClassMethods
|
||||
|
||||
module GeneratedClassMethods
|
||||
def due_at_timestamp_fields; end
|
||||
def due_at_timestamp_fields=(value); end
|
||||
def due_at_timestamp_fields?; end
|
||||
end
|
||||
|
||||
module GeneratedInstanceMethods
|
||||
def due_at_timestamp_fields; end
|
||||
def due_at_timestamp_fields=(value); end
|
||||
def due_at_timestamp_fields?; end
|
||||
end
|
||||
end
|
||||
11
sorbet/rbi/dsl/rails/application_controller.rbi
generated
11
sorbet/rbi/dsl/rails/application_controller.rbi
generated
@@ -29,16 +29,19 @@ class Rails::ApplicationController
|
||||
include ::Webpacker::Helper
|
||||
include ::ActionController::Base::HelperMethods
|
||||
include ::ApplicationHelper
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::DescriptionsHelper
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::ModelHelper
|
||||
include ::Domain::PaginationHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::DomainSourceHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
|
||||
11
sorbet/rbi/dsl/rails/conductor/base_controller.rbi
generated
11
sorbet/rbi/dsl/rails/conductor/base_controller.rbi
generated
@@ -29,16 +29,19 @@ class Rails::Conductor::BaseController
|
||||
include ::Webpacker::Helper
|
||||
include ::ActionController::Base::HelperMethods
|
||||
include ::ApplicationHelper
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::DescriptionsHelper
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::ModelHelper
|
||||
include ::Domain::PaginationHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::DomainSourceHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
|
||||
11
sorbet/rbi/dsl/rails/health_controller.rbi
generated
11
sorbet/rbi/dsl/rails/health_controller.rbi
generated
@@ -29,16 +29,19 @@ class Rails::HealthController
|
||||
include ::Webpacker::Helper
|
||||
include ::ActionController::Base::HelperMethods
|
||||
include ::ApplicationHelper
|
||||
include ::HelpersInterface
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::PathsHelper
|
||||
include ::Domain::DomainsHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::DescriptionsHelper
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::ModelHelper
|
||||
include ::Domain::PaginationHelper
|
||||
include ::Domain::PostGroupsHelper
|
||||
include ::LogEntriesHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::Domain::PostsHelper
|
||||
include ::DomainSourceHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
|
||||
42
sorbet/tapioca/compilers/has_timestamps_with_due_at_dsl.rb
Normal file
42
sorbet/tapioca/compilers/has_timestamps_with_due_at_dsl.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# typed: strict
|
||||
module Tapioca::Compilers
|
||||
class HasTimestampsWithDueAtDsl < Tapioca::Dsl::Compiler
|
||||
extend T::Sig
|
||||
|
||||
ConstantType =
|
||||
type_member { { fixed: T.class_of(::HasTimestampsWithDueAt) } }
|
||||
|
||||
sig { override.returns(T::Enumerable[Module]) }
|
||||
def self.gather_constants
|
||||
all_classes.select { |c| c < ::HasTimestampsWithDueAt }
|
||||
end
|
||||
|
||||
sig { override.void }
|
||||
def decorate
|
||||
root.create_path(constant) do |klass|
|
||||
klass.create_method(
|
||||
"attr_json_due_timestamp",
|
||||
parameters: [
|
||||
create_param("field", type: "Symbol"),
|
||||
create_param("interval", type: "ActiveSupport::Duration"),
|
||||
],
|
||||
class_method: true,
|
||||
return_type: "void",
|
||||
)
|
||||
|
||||
fields =
|
||||
T.cast(
|
||||
T.unsafe(constant).due_at_timestamp_fields || {},
|
||||
T::Hash[Symbol, ActiveSupport::Duration],
|
||||
)
|
||||
|
||||
fields.each do |field, interval|
|
||||
klass.create_method(
|
||||
"#{field}_scan",
|
||||
return_type: "HasTimestampsWithDueAt::TimestampScanInfo",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user