feat: support per-keyword rank tracking interval overrides - #40
Conversation
Adds optional per-keyword check intervals (manual/daily/weekly) on top of the existing config-level schedule, with a migration, service + schema support, cron wiring that advances both config and keyword schedules, UI columns and a toolbar control, and service tests. Closes every-app#37.
|
Hey @mvanhorn , thanks for you other contributions! Just giving you a heads up that I'm going to let this one sit for a few weeks because I want to give this feature more thought. |
| @@ -0,0 +1,3 @@ | |||
| ALTER TABLE `rank_tracking_keywords` ADD `schedule_interval_override` text DEFAULT 'inherit' NOT NULL;--> statement-breakpoint | |||
There was a problem hiding this comment.
Migration version collision
This migration is numbered 0010, but the repo already has drizzle/0010_high_liz_osborn.sql and later migrations through 0025. On databases that have applied the existing history, adding another lower-numbered 0010 leaves this schema change outside the current migration sequence, so schedule_interval_override and next_check_at will be missing when the new code queries or updates them. Please regenerate this as the next migration after the current head.
Artifacts
Repro: generated migration collision check harness
- Contains supporting evidence from the run (text/javascript; charset=utf-8).
Repro: migration collision check output
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: drizzle/0010_per_keyword_intervals.sql
Line: 1
Comment:
**Migration version collision**
This migration is numbered `0010`, but the repo already has `drizzle/0010_high_liz_osborn.sql` and later migrations through `0025`. On databases that have applied the existing history, adding another lower-numbered `0010` leaves this schema change outside the current migration sequence, so `schedule_interval_override` and `next_check_at` will be missing when the new code queries or updates them. Please regenerate this as the next migration after the current head.
How can I resolve this? If you propose a fix, please make it concise.* chore: serve Docker self-hosting with vite preview * save
sorcerai
left a comment
There was a problem hiding this comment.
Current head is not merge-safe: drizzle/0010_per_keyword_intervals.sql collides with the existing 0010 migration; the change updates SQLite schema only (no src/db/pg parity or PG migration); and service/cron due logic narrows intervals to daily|weekly, breaking existing monthly configurations and inherited monthly keywords. Please regenerate migrations at the next sequence for both dialects, preserve monthly semantics end to end, and rerun focused interval/parity tests.
@sorcerai flagged three things. The migration moves off the colliding 0010 to 0026, the next sequence per drizzle/meta/_journal.json, with its journal entry and snapshot. ScheduledInterval regains "monthly" so existing monthly configs and inherited monthly keywords are no longer narrowed to daily or weekly. PG parity is not included: the repo has no Postgres dialect, schema, or migration location to generate into.
|
daf88d1 covers two of the three. The migration is renumbered to 0026, which is the next sequence per PG parity I could not do, and I would rather ask than guess: both |
Main moved a long way since this branch, so this is a port rather than a textual merge: - Migration renumbered. Main reached 0036, so the interval migration was regenerated with drizzle-kit as 0037 rather than renumbered by hand, and the snapshot and journal come from the generator. - Postgres dialect added on main after this branch, so the same two columns and index are mirrored into src/db/pg/app.schema.ts with migration 0014. - Main extracted the cron body into runScheduledRankChecks. The per-keyword due filtering moved into that service instead of staying inline in server.ts. - Main switched useRankTrackingColumns to an options object and added a location-aware volume column; the interval column adopts both. - Main moved the filter helpers into RankTrackingFilters.logic.ts. The duplicates here were dropped and applyFilters was made generic in the row type so callers passing schedule-carrying rows get them back unwidened. - Keyword scheduling split out of RankTrackingService into keywordScheduling.ts, with getValidatedConfig moved to its own module so the two do not form an import cycle. The DomainDetail schedule state moved into a useKeywordSchedules hook. Both were over the repo's size limits after the merge. tsc, oxlint --type-aware and vitest (772 tests) all pass.
prettier formatting, and drop the flat re-exports from RankTrackingService that knip flagged as unused. The RankTrackingService object is the public surface, so callers that want the scheduling helpers directly now import them from keywordScheduling, and the duplicated interval types are gone in favour of the ones in @/types/schemas/rank-tracking.
|
@sorcerai all three are addressed, and main having moved on since your review answered the parity question I had left open. Migration collision. Rather than renumber by hand I dropped my migration, merged main, and regenerated with drizzle-kit. Main had reached 0036, so it came out as PG parity. This is the part I said I would rather ask about than guess. Main has since added Monthly semantics. Main had moved far enough that this was a port rather than a merge, so worth flagging what moved:
|
Summary
Adds optional per-keyword rank-check intervals on top of the existing config-level schedule, so a tracked keyword can be checked on its own cadence (manual / daily / weekly) independent of its config default.
Why this matters
Today the check cadence is fixed at the config (domain) level, so every keyword under a domain is forced onto the same schedule. Issue #37 asks for control at the keyword level: high-priority terms often warrant daily checks while the long tail is fine weekly (or only on demand), and checking everything at the fastest cadence wastes rank-API budget. Per-keyword overrides let users spend checks where they matter without splitting a domain across multiple configs.
What changed
drizzle/0010_per_keyword_intervals.sql,src/db/app.schema.ts): a nullable per-keywordscheduleIntervalOverridethat falls back to the config interval when unset.RankTrackingService.ts): resolve the effective interval per keyword, compute and advance per-keywordnextCheckAt, and select the keywords actually due in a scheduled run.src/server.ts): the scheduled run advances both the config schedule and the per-keyword schedules, and skips manual configs/keywords so they are never auto-advanced.RankTrackingColumns.tsx,RankTrackingTableToolbar.tsx, and the domain/table views): surface and edit the per-keyword interval, with a toolbar control for bulk changes.RankTrackingService.test.ts): cover interval resolution, schedule advancement, and the manual no-advance case.Verification
tsc --noEmitpasses.vitest run RankTrackingServicepasses (3 tests).Closes #37
AI was used for assistance.
Greptile Summary
This PR adds per-keyword rank tracking schedule overrides. The main changes are:
Confidence Score: 4/5
Safe after the migration sequencing issue is fixed.
The scheduling and UI changes are coherent, but the migration filename creates a real schema deployment mismatch for the new columns.
drizzle/0010_per_keyword_intervals.sqlWhat T-Rex did
Important Files Changed
0010migration and sits behind current migration history.rankTrackingKeywordswith per-keyword interval override and next-check timestamp fields.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant User participant UI as RankTracking UI participant API as /api/rank-tracking/keyword-intervals participant Service as RankTrackingService participant DB as rank_tracking_keywords participant Cron as Scheduled cron participant Workflow as RankCheckWorkflow User->>UI: choose keyword interval override UI->>API: PATCH projectId/configId/keywordIds/override API->>Service: updateKeywordScheduleOverride() Service->>DB: set schedule_interval_override and next_check_at Cron->>Service: getDueKeywordsForScheduledRun(config, keywords, now) Service-->>Cron: due keyword subset Cron->>Service: advanceKeywordSchedulesForScheduledRun(dueKeywords) Service->>DB: advance per-keyword next_check_at Cron->>Workflow: beginRankCheckRun(keywordIds)%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant User participant UI as RankTracking UI participant API as /api/rank-tracking/keyword-intervals participant Service as RankTrackingService participant DB as rank_tracking_keywords participant Cron as Scheduled cron participant Workflow as RankCheckWorkflow User->>UI: choose keyword interval override UI->>API: PATCH projectId/configId/keywordIds/override API->>Service: updateKeywordScheduleOverride() Service->>DB: set schedule_interval_override and next_check_at Cron->>Service: getDueKeywordsForScheduledRun(config, keywords, now) Service-->>Cron: due keyword subset Cron->>Service: advanceKeywordSchedulesForScheduledRun(dueKeywords) Service->>DB: advance per-keyword next_check_at Cron->>Workflow: beginRankCheckRun(keywordIds)Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat: per-keyword rank-check intervals f..." | Re-trigger Greptile