mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 17:53:55 +08:00
Previously, the RSS Polling plugin's admin page predated Discourse's admin interface guidelines: it was a single inline-editable table with no breadcrumbs or page header, it truncated long feed URLs, it required horizontal scrolling on mobile, and it gave admins no way to verify a feed before saving it. This change rebuilds the page on the standard plugin "show route" structure (breadcrumbs, page header, and Settings + Feeds tabs) with a `d-table` feed list and dedicated FormKit new/edit routes, so the UI now matches the rest of the admin interface and works on mobile. It also: - Adds a **Test feed** dry-run that fetches a feed and previews which items would be imported or skipped, with plain-language reasons, so admins can validate a feed (and fix setup errors) before saving it. - Adds the `rss_polling_feed_request_timeout` and `rss_polling_verbose_logging` site settings. - Extracts the fetch/parse and import/skip logic into `FeedFetcher` and `FeedAnalyzer`, shared by the poll job and the new preview so the dry-run and the real import always agree. - Normalizes feed dates and categories so Atom feeds (which use `<updated>` and `term`-style `<category>` elements) are handled like RSS, and returns a clean error instead of a 500 when a feed cannot be read. --------- Co-authored-by: Martin Brennan <martin@discourse.org>
76 lines
2.3 KiB
Ruby
Vendored
76 lines
2.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module DiscourseRssPolling
|
|
class PollAttempt < ActiveRecord::Base
|
|
self.table_name = "discourse_rss_polling_poll_attempts"
|
|
|
|
belongs_to :rss_feed, class_name: "DiscourseRssPolling::RssFeed"
|
|
|
|
enum :status, { success: 0, error: 1 }
|
|
|
|
KEEP_PER_FEED = 20
|
|
MAX_ITEMS = 50
|
|
|
|
scope :recent, -> { order(created_at: :desc, id: :desc) }
|
|
|
|
def self.record!(rss_feed_id:, items:, error: nil)
|
|
counts = items.map { |item| item["status"] }.tally
|
|
failed_count = counts.fetch("failed", 0)
|
|
|
|
attempt =
|
|
transaction do
|
|
create!(
|
|
rss_feed_id:,
|
|
status: (error || failed_count.positive?) ? :error : :success,
|
|
imported_count: counts.fetch("imported", 0),
|
|
updated_count: counts.fetch("updated", 0),
|
|
skipped_count: counts.fetch("skipped", 0),
|
|
failed_count:,
|
|
error:,
|
|
items: items.first(MAX_ITEMS),
|
|
).tap { purge_old(rss_feed_id) }
|
|
end
|
|
|
|
publish(attempt)
|
|
attempt
|
|
end
|
|
|
|
def self.message_bus_channel(rss_feed_id)
|
|
"/rss-polling/feeds/#{rss_feed_id}"
|
|
end
|
|
|
|
def self.publish(attempt)
|
|
MessageBus.publish(
|
|
message_bus_channel(attempt.rss_feed_id),
|
|
PollAttemptSerializer.new(attempt, root: false).as_json,
|
|
group_ids: [Group::AUTO_GROUPS[:admins]],
|
|
)
|
|
end
|
|
|
|
def self.purge_old(rss_feed_id)
|
|
keep_ids = where(rss_feed_id:).recent.limit(KEEP_PER_FEED).select(:id)
|
|
where(rss_feed_id:).where.not(id: keep_ids).delete_all
|
|
end
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: discourse_rss_polling_poll_attempts
|
|
#
|
|
# id :bigint not null, primary key
|
|
# error :text
|
|
# failed_count :integer default(0), not null
|
|
# imported_count :integer default(0), not null
|
|
# items :jsonb not null
|
|
# skipped_count :integer default(0), not null
|
|
# status :integer default("success"), not null
|
|
# updated_count :integer default(0), not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# rss_feed_id :bigint not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# idx_rss_polling_poll_attempts_on_feed_created_id_desc (rss_feed_id,created_at DESC,id DESC)
|
|
#
|