Enhance PostsHelper and View Logic for Improved Post Metadata Display

- Updated `PostsHelper` to enforce strict typing and added new methods for guessing HTTP log entries related to scanned posts and file downloads.
- Refactored the `post_state_string` method to handle unknown states more gracefully.
- Modified the view template to replace the old scanned and file description logic with links to log entries, providing clearer metadata about post actions.
- Removed deprecated tests related to the old description methods and added new tests for the updated functionality.

These changes improve the clarity and usability of post metadata in the application.
This commit is contained in:
Dylan Knutson
2025-01-20 17:57:58 +00:00
parent 557258ff9f
commit 0700adaa55
6 changed files with 1009 additions and 86 deletions

View File

@@ -1,15 +1,29 @@
# typed: false
# typed: strict
module Domain::Fa::PostsHelper
extend T::Sig
include ActionView::Helpers::DateHelper
include ActionView::Helpers::SanitizeHelper
include ActionView::Helpers::RenderingHelper
include ActionView::Helpers::TagHelper
sig { params(post: Domain::Fa::Post).returns(String) }
def post_state_string(post)
if post.have_file?
"file"
elsif post.scanned?
"scanned"
else
post.state
post.state || "unknown"
end
end
sig do
params(
params:
T.any(ActionController::Parameters, T::Hash[T.untyped, T.untyped]),
).returns(T.nilable(String))
end
def page_str(params)
if (params[:page] || 1).to_i > 1
"(page #{params[:page]})"
@@ -18,23 +32,21 @@ module Domain::Fa::PostsHelper
end
end
def scanned_and_file_description(post)
parts = []
if post.scanned?
time_ago =
(post.scanned_at ? time_ago_in_words(post.scanned_at) : "(unknown)")
parts << "Scanned #{time_ago} ago"
else
parts << "Not scanned"
end
if post.file
parts << "file #{time_ago_in_words(post.file.created_at)} ago"
else
parts << "no file"
end
parts.join(", ")
sig { params(post: Domain::Fa::Post).returns(T.nilable(HttpLogEntry)) }
def guess_scanned_http_log_entry(post)
HttpLogEntry.find_all_by_uri(
"https://www.furaffinity.net/view/#{post.fa_id}",
).first
end
sig { params(post: Domain::Fa::Post).returns(T.nilable(HttpLogEntry)) }
def guess_file_downloaded_http_log_entry(post)
if (uri = post.file_uri)
HttpLogEntry.find_all_by_uri(uri).first
end
end
sig { params(html: String).returns(String) }
def fa_post_description_sanitized(html)
fa_post_id_to_node = {}
fa_user_url_name_to_node = {}
@@ -49,7 +61,7 @@ module Domain::Fa::PostsHelper
properties: %w[font-size color],
},
transformers: [
lambda do |env|
Kernel.lambda do |env|
# Only allow and transform FA links
if env[:node_name] == "a"
node = env[:node]
@@ -66,12 +78,12 @@ module Domain::Fa::PostsHelper
fa_user_matcher = %r{^/user/(\w+)/?$}
if fa_host_matcher.match?(uri.host) && path
if path.match?(fa_post_matcher)
fa_id = path.match(fa_post_matcher)[1].to_i
if match = path.match(fa_post_matcher)
fa_id = match[1].to_i
fa_post_id_to_node[fa_id] = node
next { node_whitelist: [node] }
elsif path.match?(fa_user_matcher)
fa_url_name = path.match(fa_user_matcher)[1]
elsif match = path.match(fa_user_matcher)
fa_url_name = match[1]
fa_user_url_name_to_node[fa_url_name] = node
next { node_whitelist: [node] }
end

View File

@@ -275,7 +275,7 @@ class Domain::Fa::Parser::SubmissionParserHelper < Domain::Fa::Parser::Base
@elem.css(".tags-row .tags a").map(&:text).map(&:strip)
else
unimplemented_version!
end
end&.reject(&:empty?)
end
private

View File

@@ -47,8 +47,27 @@
</span>
</div>
<% if policy(@post).view_scraper_metadata? %>
<% if (scan_desc = scanned_and_file_description(@post)) %>
<div class="mt-2 text-sm text-slate-500"><%= scan_desc %></div>
<% if hle = guess_scanned_http_log_entry(@post) %>
<div class="mt-2 text-sm text-slate-500">
<%= link_to "Scanned #{time_ago_in_words(hle.requested_at)} ago",
log_entry_path(hle),
class: "text-blue-600 hover:underline",
target: "_blank" %>
</div>
<% else %>
<div class="mt-2 text-sm text-slate-500">Unknown when post scanned</div>
<% end %>
<% if hle = guess_file_downloaded_http_log_entry(@post) %>
<div class="mt-2 text-sm text-slate-500">
<%= link_to "File downloaded #{time_ago_in_words(hle.requested_at)} ago",
log_entry_path(hle),
class: "text-blue-600 hover:underline",
target: "_blank" %>
</div>
<% else %>
<div class="mt-2 text-sm text-slate-500">
Unknown when file downloaded
</div>
<% end %>
<% end %>
</section>
@@ -86,8 +105,6 @@
<% end %>
</section>
<% end %>
<%= render "section_description", { post: @post } %>
<%= render "section_similar_posts", { post: @post } %>
</div>

View File

@@ -52,65 +52,6 @@ RSpec.describe Domain::Fa::PostsHelper, type: :helper do
end
end
describe "#scanned_and_file_description" do
let(:post) { build(:domain_fa_post) }
let(:file) { double("file", created_at: 1.day.ago) }
context "when post is scanned with known time" do
before do
allow(post).to receive(:scanned?).and_return(true)
allow(post).to receive(:scanned_at).and_return(2.hours.ago)
end
it "includes scan time" do
allow(post).to receive(:file).and_return(nil)
expect(helper.scanned_and_file_description(post)).to include(
"Scanned about 2 hours ago",
)
end
end
context "when post is scanned but time unknown" do
before do
allow(post).to receive(:scanned?).and_return(true)
allow(post).to receive(:scanned_at).and_return(nil)
end
it "shows unknown scan time" do
allow(post).to receive(:file).and_return(nil)
expect(helper.scanned_and_file_description(post)).to include(
"Scanned (unknown) ago",
)
end
end
context "when post has file" do
before do
allow(post).to receive(:scanned?).and_return(false)
allow(post).to receive(:file).and_return(file)
end
it "includes file time" do
expect(helper.scanned_and_file_description(post)).to include(
"file 1 day ago",
)
end
end
context "when post has neither scan nor file" do
before do
allow(post).to receive(:scanned?).and_return(false)
allow(post).to receive(:file).and_return(nil)
end
it "shows appropriate message" do
expect(helper.scanned_and_file_description(post)).to eq(
"Not scanned, no file",
)
end
end
end
describe "#fa_post_description_sanitized" do
describe "basic HTML sanitization" do
it "works" do

View File

@@ -465,6 +465,35 @@ describe Domain::Fa::Parser::Page do
assert_nil parser.favorites_next_button_id
end
it "parses 2025 format" do
parser = get_parser("submission_59584979_2025_style_keywords.html")
assert_page_type parser, :probably_submission?
sub = parser.submission
assert_equal 59_584_979, sub.id
assert_equal "Felureii", sub.artist
assert_equal "/user/felureii/", sub.artist_user_page_path
assert_equal "//a.furaffinity.net/1733967392/felureii.gif",
sub.artist_avatar_url
assert_equal "Practice", sub.title
assert_equal :general, sub.rating
assert_equal "//d.furaffinity.net/art/felureii/1737390216/1737390216.felureii_0f3gn7ifbl8.jpg",
sub.small_img
assert_equal "//d.furaffinity.net/art/felureii/1737390216/1737390216.felureii_0f3gn7ifbl8.jpg",
sub.full_res_img
assert_equal Time.zone.parse("Jan 20, 2025 11:23"), sub.posted_date
assert_equal "Artwork (Digital)", sub.category
assert_equal "All", sub.theme
assert_equal "Unspecified / Any", sub.species
assert_equal "Any", sub.gender
assert_equal 0, sub.num_favorites
assert_equal 0, sub.num_comments
assert_equal 16, sub.num_views
assert_equal "677x672", sub.resolution_str
assert_equal %w[art troll fantasy], sub.keywords_array
assert_equal "Nothing special, just practice.", sub.description_html.strip
end
def get_parser(file, require_logged_in: true)
path = File.join("domain/fa/parser/redux", file)
contents =

File diff suppressed because one or more lines are too long