ff-698 (gh-225) — Collect and visualize feed activity statistics. - #527
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces backend collection and frontend visualization of per-feed activity statistics (daily loaded entries, average news/day, "young feed" flag, site URL). It also restructures the Feeds view UI to mirror the News view (clickable rows with detail bodies, a 30-day activity chart using chart.js/vue-chartjs), refactors product-specific user settings out of ffun.user_settings into ffun.product, introduces a SettingKind NewType, and updates feed parsing/storage to capture a feed's site URL. A new l_feed_entries_count table tracks per-day per-feed entry counts, and a new /get-feeds-by-ids private API serves on-demand full feed details. Specs (tests.md, modules_layout.md, errors.md) are updated to formalize the new module structure and rules. A new Donna inconsistency-check workflow and supporting tooling are added.
Changes:
- Backend: new
site_url, daily entries counts, feed statistics utilities,SettingKindtyping, refactor offfun.product(UserSetting/Resource moved toentities.py), newget-feeds-by-idsendpoint. - Frontend: new Vue components (
feed_list/Columns.vue,FeedEntriesPerDayChart.vue),feedsstore rewritten for on-demand full-feed loading, new tests, chart.js/vue-chartjs dependency, dropped "show feed descriptions" toggle. - Spec/tooling: updated
specs/backend_architecture/*.md, new Donna workflow +consistency.toml, changelog.
Reviewed changes
Copilot reviewed 75 out of 77 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ffun/ffun/library/migrations/20260527_01_c0DeX-feed-entry-counts.py | New l_feed_entries_count table. |
| ffun/ffun/feeds/migrations/20260529_01_c0DeX-feed-site-url.py | Adds site_url column to f_feeds. |
| ffun/ffun/library/operations.py | _increment_feed_entries_count, remove_feed_entries_count, entries_in_period(_details). |
| ffun/ffun/library/domain.py / tests | Re-exports plus test coverage for new operations. |
| ffun/ffun/feeds/operations.py / entities.py / utils.py / domain.py + tests | New site_url/created_at columns, entries_per_day/is_young helpers. |
| ffun/ffun/parsers/feed.py / opml.py / entities.py + tests + fixtures | Extracts feed site URL from RSS/Atom/OPML. |
| ffun/ffun/loader/domain.py + tests | Syncs site_url along with title/description. |
| ffun/ffun/meta/domain.py + tests | Removes per-feed counts when orphaned feeds are cleaned. |
| ffun/ffun/product/entities.py / user_settings.py / resources.py | Moves UserSetting and Resource enums into entities.py. |
| ffun/ffun/user_settings/entities.py / types.py / operations.py / domain.py / values.py + tests | Introduces SettingKind NewType and TypeId enum relocation. |
| ffun/ffun/feeds_collections/domain.py | New thin facade over the collections singleton. |
| ffun/ffun/llms_framework/{entities,domain,keys_rotator}.py + tests/fixtures | Adds LLMCostPoints NewType and public cost_points_to_usd_cost. |
| ffun/ffun/api/spa/{entities,http_handlers,settings}.py + tests | New get-feeds-by-ids endpoint, _external_feeds, feed metrics fields, settings refactor. |
| ffun/ffun/core/tests/test_middlewares.py | Registers new endpoint path. |
| ffun/ffun/cli/commands/metrics.py | Updated import path. |
| ffun/ffun/domain/entities.py | Adds Days NewType. |
| site/src/logic/types.ts + tests | Adds siteUrl, young, entriesPerDay, entriesLoadedDetails; nullable linkedAt. |
| site/src/logic/enums.ts | Adds EntriesPerDay order. |
| site/src/logic/api.ts | New getFeedsByIds client. |
| site/src/stores/feeds.ts + tests | Store rewritten: on-demand full-feed loading via Timer queue. |
| site/src/stores/globalSettings.ts | Removes showFeedsDescriptions. |
| site/src/views/FeedsView.vue | Side menu reshuffled; remove descriptions toggle. |
| site/src/components/FeedsList.vue / FeedForList.vue | New column layout, click-to-expand details body. |
| site/src/components/feed_list/{Columns.vue,FeedEntriesPerDayChart.vue} | New components. |
| site/src/components/body_list/EntryBody.vue | Adds body-prefix slot. |
| site/package.json / package-lock.json | Adds chart.js + vue-chartjs deps. |
| specs/backend_architecture/{tests,modules_layout,errors}.md | Doc updates: time-baseline rule, foundational/edge module taxonomy, expected-vs-unexpected error guidance. |
| consistency.toml / workflows/inconsistency-check.donna.md / AGENTS.md / .session/inconsistency-check-plan.md | New Donna consistency-check workflow + planning artifacts. |
| changes/next_release.md / unreleased.md | Changelog entry. |
Files not reviewed (1)
- site/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function loadFullFeeds() { | ||
| const ids: t.FeedId[] = Object.keys(requestedFeeds.value).map((key) => t.toFeedId(key)); | ||
|
|
||
| if (ids.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| const loadedFeeds = await api.getFeedsByIds({ids: ids}); | ||
|
|
||
| registerFeeds({ | ||
| newFeeds: loadedFeeds, | ||
| replace: false | ||
| }); | ||
|
|
||
| requestedFeeds.value = {}; | ||
| } |
| def entries_per_day(feed: Feed, entries_loaded: int, period: Days, now: datetime.datetime | None = None) -> int: | ||
| assert feed.created_at is not None | ||
|
|
||
| if now is None: | ||
| now = core_utils.now() | ||
|
|
||
| feed_age = max(datetime.timedelta(), now - feed.created_at) | ||
| feed_age_days = math.ceil(feed_age / datetime.timedelta(days=1)) | ||
| days = max(1, min(int(period), feed_age_days)) | ||
|
|
||
| return math.ceil(entries_loaded / days) | ||
|
|
||
|
|
||
| def is_young(feed: Feed, period: Days, now: datetime.datetime | None = None) -> bool: | ||
| assert feed.created_at is not None | ||
|
|
||
| if now is None: | ||
| now = core_utils.now() | ||
|
|
||
| feed_age = max(datetime.timedelta(), now - feed.created_at) | ||
|
|
||
| return feed_age < datetime.timedelta(days=int(period)) |
No description provided.