Add generate_playtime_report collection setting to gate playtime reports (PP-4346) - #3407
Conversation
|
Claude finished @dbernstein's task in 7m 47s —— View job Code Review
SummaryThe PR cleanly replaces an unbounded-fallback design with an explicit opt-in DetailsMinor:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3407 +/- ##
=======================================
Coverage 93.40% 93.40%
=======================================
Files 504 505 +1
Lines 46234 46239 +5
Branches 6310 6310
=======================================
+ Hits 43184 43190 +6
+ Misses 1981 1980 -1
Partials 1069 1069 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
edbfb06 to
cd79138
Compare
06b33cd to
67ccd07
Compare
| # The key as it appears in settings_dict; derived from the model field to avoid bare string literals. | ||
| PLAYTIME_REPORT_FLAG_KEY: str = ( | ||
| PlaytimeReportSettings.model_fields["is_generate_playtime_report"].alias | ||
| or "is_generate_playtime_report" |
There was a problem hiding this comment.
I'm not sure why Claude added the "or" clause.
There was a problem hiding this comment.
I'm going to change that.
d8078af to
7dc1e98
Compare
|
@jonathangreen : I reworked this one according to your suggestion re using a configuration setting on the collection rather than hardcoding the data source names in the report generator. |
jonathangreen
left a comment
There was a problem hiding this comment.
I'm approving this one, but I'd to get the OPDS2WithODLSettings issue resolved before merging. Also added a minor comment to consider about variable naming.
|
|
||
|
|
||
| class OPDS2ImporterSettings(OPDSImporterSettings): | ||
| class OPDS2ImporterSettings(PlaytimeReportSettings, OPDSImporterSettings): |
There was a problem hiding this comment.
Because OPDS2WithODLSettings subclasses OPDS2ImporterSettings, adding PlaytimeReportSettings here also exposes is_generate_playtime_report on ODL 2.0 collection configuration forms. Since OPDS2+ODL collections are filtered out, it doesn't actually enable it for them, but it does create a misleading config setting.
| playtime report uploaded to Google Drive. | ||
| """ | ||
|
|
||
| is_generate_playtime_report: Annotated[ |
There was a problem hiding this comment.
Minor: I really don't like the name is_generate_playtime_report. is_generate_playtime_report is off in two ways:
is_prefix: nothing else in the settings models uses it; the convention is bare verbs.is_+ generate reads as "is generate": grammatically broken.is_wants an adjective/state (is_enabled), not a verb.
opds1/settings.py has a setting include_in_inventory_report, which serves a similar purpose, and follows the conventions we've been using more closely.
My suggestion would be to rename this to either, in order of preference:
include_in_playtime_reportgenerate_playtime_report
…rts (PP-XXXX) Replace the hardcoded data-source allowlist with an explicit opt-in flag on OPDS 2.0 and OPDS for Distributors collections: - Add `generate_playtime_report: bool = False` (SELECT form field, sysadmin) to OPDS2ImporterSettings and OPDSForDistributorsSettings. - Migration: set the flag to True for all existing OPDS 2.0 / OFD collections whose data_source name begins with "Blackstone" or "Unlimited". - `_fetch_distinct_eligible_data_source_names` now filters to only collections where the flag is True; the PlaytimeSummary fallback (root cause of folder proliferation) is removed. - Tests updated: flag-based parametrised cases replace the old playtime-summary cases; deleted-collection test now asserts the new (no-fallback) behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ndant null guard - Migration: replace tuple-binding (psycopg2-specific) with bindparam(..., expanding=True) for the protocol IN clause in both upgrade() and downgrade(), which is the portable SQLAlchemy approach. - playtime_entries.py: remove the redundant `c.integration_configuration` null check in the set comprehension — the query filter already guarantees every returned Collection has an IntegrationConfiguration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ytime_report - Add PlaytimeReportSettings mixin in integration/license/opds/settings/playtime_report.py following the same pattern as SAMLWAYFlessSetttings and FormatPrioritiesSettings. - Both OPDS2ImporterSettings and OPDSForDistributorsSettings now inherit from PlaytimeReportSettings; the duplicated field definition is removed. The field remains absent from OPDSImporterSettings (OPDS 1.x base). - Rename the field and its settings_dict key from generate_playtime_report to is_generate_playtime_report throughout (task, migration, tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ring Export PLAYTIME_REPORT_FLAG_KEY from PlaytimeReportSettings, derived from the model field's alias, and use it in _fetch_distinct_eligible_data_source_names to avoid a bare string literal that could silently drift from the field name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The conflict resolution during rebase introduced generate_playtime_report=True instead of the renamed is_generate_playtime_report=True in test_generate_playtime_report_folder_lock_contention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The alias-derived constant adds complexity without real benefit — FieldInfo.alias is None when no alias is set, so the fallback literal was always supplying the value anyway. A plain string lookup is simpler and equally clear. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The is_ prefix followed by a verb is grammatically broken and inconsistent with the convention used by other settings fields (e.g. include_in_inventory_report). generate_playtime_report follows the bare-verb convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7dc1e98 to
b29e0a2
Compare
…g to ODL 2.0 OPDS2WithODLSettings inherits from OPDS2ImporterSettings, so placing PlaytimeReportSettings in OPDS2ImporterSettings caused the generate_playtime_report field to appear in ODL 2.0 collection config forms — where it does nothing, since OPDS2WithODLApi is excluded from the eligible-protocols list. Fix: remove PlaytimeReportSettings from OPDS2ImporterSettings and introduce a thin OPDS2APISettings(PlaytimeReportSettings, OPDS2ImporterSettings) subclass used exclusively by OPDS2API. OPDS2WithODLSettings continues to inherit from OPDS2ImporterSettings and remains unaffected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Description
Replaces the current behavior of generating reports for every OPDS2 and OPDSForDistributors protocol with an explicit per-collection opt-in flag that controls whether a collection's playtime data is included in the monthly Google Drive report.
Changes
New setting:
generate_playtime_report(bool, defaultFalse)Added via the
PlaytimeReportSettingsmixin to:OPDS2APISettings— the settings class used exclusively byOPDS2API(OPDS 2.0 Import)OPDSForDistributorsSettings— the settings class used byOPDSForDistributorsAPIThe field is not present on
OPDS2ImporterSettings(the shared base class), which meansOPDS2WithODLSettings(ODL 2.0) does not inherit it and the setting does not appear on ODL 2.0 collection forms.OPDS2APISettingsis a thin subclass ofOPDS2ImporterSettingsthat adds the mixin.The field is sysadmin-facing and rendered as a SELECT ("Generate playtime report for audio books"). All other protocols are ineligible by design.
Migration
Sets
generate_playtime_report = Truefor all existing OPDS 2.0 / OPDS for Distributors collections whosedata_sourcename begins with"Blackstone"or"Unlimited". All other collections default toFalseand require no update. I verified in Redshift that using the aforementioned constraints will match all Blackstone and Unlimited Listens data sources and no others.Downgrade removes the key entirely (absence is equivalent to the Pydantic default of
False)._fetch_distinct_eligible_data_source_namesNow returns only data source names for collections where
generate_playtime_reportis explicitlyTrue. ThePlaytimeSummaryfallback (which was the root cause of folder proliferation — it included every historical data source regardless of intent) has been removed.Motivation and Context
The Partner Success team observed large numbers of Google Drive folders being generated for every data source in the system. The underlying cause was a combination of:
_fetch_distinct_eligible_data_source_namesreturning all data sources that ever appeared inPlaytimeSummaryrecords, andThe flag-based approach gives sysadmins explicit control, makes the opt-in visible and auditable in the collection settings UI, and removes the unbounded fallback.
JIRA: PP-4346
How Has This Been Tested?
test_fetch_distinct_eligible_data_source_names(8 parametrised cases): verifies that only flagged OPDS 2.0/OFD collections are included; unflagged collections, ineligible protocols, and mixed scenarios all behave correctly.test_fetch_distinct_eligible_data_source_names_deleted_collection: verifies the new no-fallback behaviour — a deleted collection is excluded even if it had prior playtime data.test_generate_playtime_reports: updated to setgenerate_playtime_report=Trueon active collections and verify that the deletedcollection4(ds_d) no longer produces a report (3 files instead of 4).test_generate_playtime_report_folder_lock_contention: verifies that the task completes and logs a warning when the folder-creation lock cannot be acquired (from PR Reduce Google Drive folder race condition with Redis lock, jitter, and pick-oldest (PP-4434) #3377, now included here after rebase).Checklist