Conversation
WalkthroughAdds per-automation dry-run support end-to-end: DB migration and model fields, API payload, service orchestration to record dry-run activities without mutating state, tests, export/import and type updates, and UI changes to toggle and display dry-run results. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as WorkflowDialog
participant API as API Handler
participant Service as Automation Service
participant ActivityStore as Activity Store
participant DB as Database
User->>UI: Toggle Enable + Dry run
UI->>API: POST/PUT automation { dryRun: true }
API->>Service: Create/Update + trigger apply flow
Service->>Service: Split apply -> dry-run branch & live branch
Service->>ActivityStore: recordDryRunActivities(pending items)
ActivityStore->>DB: INSERT activities (outcome="dry-run")
DB-->>ActivityStore: OK
Service-->>API: return dry-run activity summary
API-->>UI: Response with dry-run activities
UI->>User: Display "(dry run)" badges and run details
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
web/src/lib/workflow-utils.ts (1)
175-188:⚠️ Potential issue | 🟠 MajorPreserve
dryRunwhen parsing import JSON.
parseImportJSONnever readsdryRun, so imported workflows lose the flag.✅ Suggested fix
// Optional intervalSeconds if (typeof obj.intervalSeconds === "number" && obj.intervalSeconds >= 60) { data.intervalSeconds = obj.intervalSeconds } + + // Optional dryRun + if (typeof obj.dryRun === "boolean") { + data.dryRun = obj.dryRun + } else if (obj.dryRun !== undefined) { + return { data: null, error: "Invalid 'dryRun' field" } + }web/src/types/index.ts (1)
356-379:⚠️ Potential issue | 🔴 CriticalRemove duplicate
programNamefield (TS2300).
programNameis declared twice in thedetailsobject (lines 367 and 378), causing a TypeScript compilation error. Remove the second occurrence.🛠️ Proposed fix
- programName?: string
🤖 Fix all issues with AI agents
In `@internal/services/automations/service_test.go`:
- Around line 1336-1338: Replace the explicit empty-string equality check with
testify's empty assertion: change the assertion that compares
mockDB.activities[0].Hash to "" to use assert.Empty(t,
mockDB.activities[0].Hash) so the test uses the idiomatic empty check alongside
the existing require.Len(t, mockDB.activities, 1) and assert.Equal for Action
(models.ActivityActionDeletedCondition).
In `@internal/services/automations/service.go`:
- Around line 1519-1525: The file fails gofmt; reformat the affected block
around the dryRun check and the instLastApplied update to comply with gofmt
style: run `gofmt -w` (or `gofmt -w internal/services/automations/service.go`)
or use your editor's Go formatter so the code around the `if !dryRun { ...
s.mu.Lock(); instLastApplied[h] = now; s.mu.Unlock() }` block is properly
formatted; ensure no stray spacing or line breaks remain so the linter/pipeline
passes.
- Around line 1032-1049: The current sequence runs live rules before dry-run
rules causing live updates to instLastApplied and making dry-run rules skip
recent torrents; swap the calls so s.applyRulesForInstance(ctx, instanceID,
force, dryRunRules, true) runs before s.applyRulesForInstance(ctx, instanceID,
force, liveRules, false) to ensure dry-run rules evaluate all torrents
(reference variables liveRules, dryRunRules and function applyRulesForInstance,
and the instLastApplied/SkipWithin behavior).
🧹 Nitpick comments (1)
web/src/components/instances/preferences/WorkflowsOverview.tsx (1)
243-253: Consider simplifying the nested ternary for readability.The deeply nested ternary operators can be harder to read and maintain. A mapping object would be cleaner:
♻️ Suggested refactor
function formatDeleteDryRunSummary(details: AutomationActivity["details"], action: AutomationActivity["action"]): string { const count = details?.count ?? 0 - const label = action === "deleted_ratio" - ? "ratio limit" - : action === "deleted_seeding" - ? "seeding limit" - : action === "deleted_unregistered" - ? "unregistered" - : "condition" + const labelMap: Record<string, string> = { + deleted_ratio: "ratio limit", + deleted_seeding: "seeding limit", + deleted_unregistered: "unregistered", + deleted_condition: "condition", + } + const label = labelMap[action] ?? "condition" return `${count} torrent${count !== 1 ? "s" : ""} would be deleted (${label})` }
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@web/src/components/instances/preferences/WorkflowsOverview.tsx`:
- Around line 187-253: The dry-run verbs should use passive voice for
consistency: update formatPausedSummary to set verb to "would be paused" when
outcome === "dry-run", update formatCategoryChangedSummary to set verb to "would
be moved" for dry-run, and update formatMovedSummary to use "would be moved" for
dry-run (keeping the existing failed branch using "failed to move"); these
changes are in the functions formatPausedSummary, formatCategoryChangedSummary,
and formatMovedSummary and rely on the existing formatCountWithVerb helper to
produce the final string.
|
Since pr-1362, when I try to go back to develop, I get the following error: |
Please dont run PRs like this without knowing the consequences. Easiest fix for you right now is to shut down qui, delete qui.db + qui.db-shm and qui.db-wal. Then start qui again. Alternatively you could try this: Shut down qui. sqlite3 -cmd ".timeout 5000" qui.db "PRAGMA table_info(automations);"
sqlite3 -cmd ".timeout 5000" qui.db "SELECT filename, applied_at FROM migrations WHERE filename LIKE '058%';"If automations already has dry_run but no row for 058_add_automation_dry_run.sql, just mark it applied: sqlite3 -cmd ".timeout 5000" qui.db \
"INSERT OR IGNORE INTO migrations (filename) VALUES ('058_add_automation_dry_run.sql');"If you see some other 058 row then rename that row: sqlite3 -cmd ".timeout 5000" qui.db \
"UPDATE migrations SET filename='058_add_automation_dry_run.sql' WHERE filename='058_add_dryrun.sql';" |
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.13.1` → `v1.14.0` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.14.0`](https://github.com/autobrr/qui/releases/tag/v1.14.0) [Compare Source](autobrr/qui@v1.13.1...v1.14.0) #### Changelog ##### New Features - [`6f8e6ed`](autobrr/qui@6f8e6ed): feat(api): add torrent field endpoint for select all copy ([#​1477](autobrr/qui#1477)) ([@​jabloink](https://github.com/jabloink)) - [`2d9b4c7`](autobrr/qui@2d9b4c7): feat(automation): trigger external programs automatically via automation rules ([#​1284](autobrr/qui#1284)) ([@​0rkag](https://github.com/0rkag)) - [`32692a4`](autobrr/qui@32692a4): feat(automations): Add the ability to define the move automation with a templated path ([#​1376](autobrr/qui#1376)) ([@​ColinHebert](https://github.com/ColinHebert)) - [`61bbeb1`](autobrr/qui@61bbeb1): feat(automations): add Resume action to Automations ([#​1350](autobrr/qui#1350)) ([@​cy1der](https://github.com/cy1der)) - [`450b98f`](autobrr/qui@450b98f): feat(automations): grouping + release fields ([#​1467](autobrr/qui#1467)) ([@​s0up4200](https://github.com/s0up4200)) - [`18d4a64`](autobrr/qui@18d4a64): feat(automations): match tracker conditions by display name ([#​1420](autobrr/qui#1420)) ([@​s0up4200](https://github.com/s0up4200)) - [`7c67b82`](autobrr/qui@7c67b82): feat(automations): show activity run details ([#​1385](autobrr/qui#1385)) ([@​s0up4200](https://github.com/s0up4200)) - [`177ef4d`](autobrr/qui@177ef4d): feat(crossseed): Multiple hard/reflink dirs ([#​1289](autobrr/qui#1289)) ([@​rybertm](https://github.com/rybertm)) - [`a72b673`](autobrr/qui@a72b673): feat(crossseed): gazelle-only OPS/RED ([#​1436](autobrr/qui#1436)) ([@​s0up4200](https://github.com/s0up4200)) - [`6a29384`](autobrr/qui@6a29384): feat(crossseed): match bit depth ([#​1427](autobrr/qui#1427)) ([@​s0up4200](https://github.com/s0up4200)) - [`c7fd5aa`](autobrr/qui@c7fd5aa): feat(dirscan): add max searchee age filter ([#​1486](autobrr/qui#1486)) ([@​s0up4200](https://github.com/s0up4200)) - [`d595a55`](autobrr/qui@d595a55): feat(documentation): add AI doc actions and llms discoverability ([#​1451](autobrr/qui#1451)) ([@​s0up4200](https://github.com/s0up4200)) - [`562ab3f`](autobrr/qui@562ab3f): feat(metrics): add tracker metrics ([#​1073](autobrr/qui#1073)) ([@​Winter](https://github.com/Winter)) - [`1b9aa9d`](autobrr/qui@1b9aa9d): feat(notifications): add shoutrrr and notifiarr ([#​1371](autobrr/qui#1371)) ([@​s0up4200](https://github.com/s0up4200)) - [`6d1dac7`](autobrr/qui@6d1dac7): feat(pwa): add protocol and file handlers for magnet links and torrent files ([#​783](autobrr/qui#783)) ([@​s0up4200](https://github.com/s0up4200)) - [`42fa501`](autobrr/qui@42fa501): feat(torrents): add unified cross-instance torrent table ([#​1481](autobrr/qui#1481)) ([@​s0up4200](https://github.com/s0up4200)) - [`498eaca`](autobrr/qui@498eaca): feat(ui): show speeds in page title ([#​1292](autobrr/qui#1292)) ([@​NoLife141](https://github.com/NoLife141)) - [`94a506e`](autobrr/qui@94a506e): feat(unregistered): nem talalhato ([#​1483](autobrr/qui#1483)) ([@​KyleSanderson](https://github.com/KyleSanderson)) - [`8bf366c`](autobrr/qui@8bf366c): feat(web): add logs nav ([#​1458](autobrr/qui#1458)) ([@​s0up4200](https://github.com/s0up4200)) - [`babc88d`](autobrr/qui@babc88d): feat(web): add responsive popover with mobile drawer support ([#​1398](autobrr/qui#1398)) ([@​jabloink](https://github.com/jabloink)) - [`06d341b`](autobrr/qui@06d341b): feat(web): add torrent table selection quick wins ([#​1455](autobrr/qui#1455)) ([@​s0up4200](https://github.com/s0up4200)) - [`56fbbec`](autobrr/qui@56fbbec): feat(web): hide selection column ([#​1460](autobrr/qui#1460)) ([@​s0up4200](https://github.com/s0up4200)) - [`46814aa`](autobrr/qui@46814aa): feat(web): qBittorrent autorun preferences ([#​1430](autobrr/qui#1430)) ([@​s0up4200](https://github.com/s0up4200)) - [`342643e`](autobrr/qui@342643e): feat(web): unify instance settings & qbit options dialog ([#​1257](autobrr/qui#1257)) ([@​0rkag](https://github.com/0rkag)) - [`e634d01`](autobrr/qui@e634d01): feat: add cross-seed blocklist ([#​1391](autobrr/qui#1391)) ([@​s0up4200](https://github.com/s0up4200)) - [`13aaac8`](autobrr/qui@13aaac8): feat: add dry-run workflows ([#​1395](autobrr/qui#1395)) ([@​s0up4200](https://github.com/s0up4200)) - [`f01101d`](autobrr/qui@f01101d): feat: add option to disable built-in authentication ([#​1464](autobrr/qui#1464)) ([@​libussa](https://github.com/libussa)) - [`6d1da50`](autobrr/qui@6d1da50): feat: download individual content files from context menu ([#​1465](autobrr/qui#1465)) ([@​libussa](https://github.com/libussa)) - [`77e9abf`](autobrr/qui@77e9abf): feat: migrate to dodopayments ([#​1407](autobrr/qui#1407)) ([@​s0up4200](https://github.com/s0up4200)) - [`9f6c856`](autobrr/qui@9f6c856): feat: support basic auth for ARR and Torznab ([#​1442](autobrr/qui#1442)) ([@​s0up4200](https://github.com/s0up4200)) ##### Bug Fixes - [`8a06d4b`](autobrr/qui@8a06d4b): fix(api): correct add-torrent OpenAPI param names and add missing fields ([#​1426](autobrr/qui#1426)) ([@​s0up4200](https://github.com/s0up4200)) - [`b9a687c`](autobrr/qui@b9a687c): fix(api): honor explicit basic auth clear from URL userinfo ([@​s0up4200](https://github.com/s0up4200)) - [`948ca67`](autobrr/qui@948ca67): fix(api): tighten CORS/auth routing and base URL joins ([#​1325](autobrr/qui#1325)) ([@​s0up4200](https://github.com/s0up4200)) - [`12bea13`](autobrr/qui@12bea13): fix(automations): improve applied action summaries ([#​1478](autobrr/qui#1478)) ([@​s0up4200](https://github.com/s0up4200)) - [`8fe658b`](autobrr/qui@8fe658b): fix(automations): negate regex match for NotContains/NotEqual operators ([#​1441](autobrr/qui#1441)) ([@​andresatierf](https://github.com/andresatierf)) - [`8a808eb`](autobrr/qui@8a808eb): fix(automations): respect remove-only tag conditions ([#​1444](autobrr/qui#1444)) ([@​s0up4200](https://github.com/s0up4200)) - [`a72715e`](autobrr/qui@a72715e): fix(backups): add failure cooldown and export throttling ([#​1214](autobrr/qui#1214)) ([@​s0up4200](https://github.com/s0up4200)) - [`2e75c14`](autobrr/qui@2e75c14): fix(backups): skip exports missing metadata ([#​1362](autobrr/qui#1362)) ([@​s0up4200](https://github.com/s0up4200)) - [`5658421`](autobrr/qui@5658421): fix(config): update commented log settings in place ([#​1402](autobrr/qui#1402)) ([@​s0up4200](https://github.com/s0up4200)) - [`62c50c0`](autobrr/qui@62c50c0): fix(crossseed): tighten TV title matching ([#​1445](autobrr/qui#1445)) ([@​s0up4200](https://github.com/s0up4200)) - [`e7cc489`](autobrr/qui@e7cc489): fix(dirscan): prevent immediate requeue after cancel ([#​1446](autobrr/qui#1446)) ([@​s0up4200](https://github.com/s0up4200)) - [`36cbfcf`](autobrr/qui@36cbfcf): fix(docs): avoid mdx jsx parse error ([@​s0up4200](https://github.com/s0up4200)) - [`d8d6f62`](autobrr/qui@d8d6f62): fix(filters): stabilize dense sidebar layout ([#​1384](autobrr/qui#1384)) ([@​s0up4200](https://github.com/s0up4200)) - [`b959fc6`](autobrr/qui@b959fc6): fix(orphanscan): NFC-normalize paths to avoid false orphans ([#​1422](autobrr/qui#1422)) ([@​s0up4200](https://github.com/s0up4200)) - [`598e994`](autobrr/qui@598e994): fix(reflink): retry EAGAIN clones ([#​1360](autobrr/qui#1360)) ([@​s0up4200](https://github.com/s0up4200)) - [`aaa5ee0`](autobrr/qui@aaa5ee0): fix(reflinktree): retry transient FICLONE EINVAL and add diagnostics ([#​1487](autobrr/qui#1487)) ([@​s0up4200](https://github.com/s0up4200)) - [`647af31`](autobrr/qui@647af31): fix(rss): enable rules list scrolling ([#​1359](autobrr/qui#1359)) ([@​s0up4200](https://github.com/s0up4200)) - [`c356a6f`](autobrr/qui@c356a6f): fix(sync): Optimize torrent sorting and reference management ([#​1474](autobrr/qui#1474)) ([@​KyleSanderson](https://github.com/KyleSanderson)) - [`cf4310e`](autobrr/qui@cf4310e): fix(ui): update placeholder text in ArrInstanceForm based on instance type ([#​1375](autobrr/qui#1375)) ([@​pashioya](https://github.com/pashioya)) - [`92b6748`](autobrr/qui@92b6748): fix(web): format IPv6 peer addresses and copy IP without port ([#​1417](autobrr/qui#1417)) ([@​sleepm](https://github.com/sleepm)) - [`25039bc`](autobrr/qui@25039bc): fix(web): handle SSO session expiry behind Cloudflare Access and other proxies ([#​1438](autobrr/qui#1438)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`77fe310`](autobrr/qui@77fe310): fix(web): prevent category submenu re-render ([#​1357](autobrr/qui#1357)) ([@​jabloink](https://github.com/jabloink)) - [`a42ab1e`](autobrr/qui@a42ab1e): fix(web): raise instance preferences max value from 999 to 99999 ([#​1311](autobrr/qui#1311)) ([@​s0up4200](https://github.com/s0up4200)) - [`540168c`](autobrr/qui@540168c): fix(web): raise virtualization threshold ([#​1355](autobrr/qui#1355)) ([@​jabloink](https://github.com/jabloink)) - [`8547dc6`](autobrr/qui@8547dc6): fix(web): remove column filters when column is hidden ([#​1418](autobrr/qui#1418)) ([@​jabloink](https://github.com/jabloink)) - [`6b09b8d`](autobrr/qui@6b09b8d): fix(web): remove panel size bounds ([@​s0up4200](https://github.com/s0up4200)) - [`db4cdc4`](autobrr/qui@db4cdc4): fix(web): show piece size in torrent details ([#​1365](autobrr/qui#1365)) ([@​s0up4200](https://github.com/s0up4200)) - [`1f94a06`](autobrr/qui@1f94a06): fix(web): use absolute for scroll-to-top on desktop ([#​1419](autobrr/qui#1419)) ([@​jabloink](https://github.com/jabloink)) - [`e31fe3a`](autobrr/qui@e31fe3a): fix: detect tracker health support after qBit upgrade ([#​909](autobrr/qui#909)) ([@​s0up4200](https://github.com/s0up4200)) - [`52f01da`](autobrr/qui@52f01da): fix: disable update indicators when update checks are off ([#​1364](autobrr/qui#1364)) ([@​s0up4200](https://github.com/s0up4200)) - [`f7e3fed`](autobrr/qui@f7e3fed): fix: normalize DD+ and DDP file keys ([#​1456](autobrr/qui#1456)) ([@​s0up4200](https://github.com/s0up4200)) ##### Other Changes - [`d914301`](autobrr/qui@d914301): chore(ci): fire Blacksmith (my wallet screamed) ([#​1408](autobrr/qui#1408)) ([@​s0up4200](https://github.com/s0up4200)) - [`b43327d`](autobrr/qui@b43327d): chore(deps): bump the golang group with 2 updates ([#​1378](autobrr/qui#1378)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`57747bd`](autobrr/qui@57747bd): chore(deps): bump the npm group across 1 directory with 27 updates ([#​1379](autobrr/qui#1379)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`a43850d`](autobrr/qui@a43850d): chore(docs): add BIMI SVG logo ([@​s0up4200](https://github.com/s0up4200)) - [`914bede`](autobrr/qui@914bede): chore(funding): add Patreon to FUNDING.yml ([@​s0up4200](https://github.com/s0up4200)) - [`8b76f1e`](autobrr/qui@8b76f1e): docs(automations): clarify tag matching examples ([#​1457](autobrr/qui#1457)) ([@​s0up4200](https://github.com/s0up4200)) - [`2994054`](autobrr/qui@2994054): docs(readme): restore concise README ([#​1452](autobrr/qui#1452)) ([@​s0up4200](https://github.com/s0up4200)) - [`51237d4`](autobrr/qui@51237d4): docs: Add configuration reference ([#​1440](autobrr/qui#1440)) ([@​s0up4200](https://github.com/s0up4200)) - [`741462c`](autobrr/qui@741462c): docs: add Windows installation guide ([#​1463](autobrr/qui#1463)) ([@​soggy-cr0uton](https://github.com/soggy-cr0uton)) - [`6a11430`](autobrr/qui@6a11430): docs: clarify autobrr filter + apply troubleshooting ([#​1459](autobrr/qui#1459)) ([@​s0up4200](https://github.com/s0up4200)) - [`5a2edc2`](autobrr/qui@5a2edc2): docs: update 2 documentation files ([#​1454](autobrr/qui#1454)) ([@​s0up4200](https://github.com/s0up4200)) - [`139ada9`](autobrr/qui@139ada9): docs: update contributing.md ([#​1470](autobrr/qui#1470)) ([@​s0up4200](https://github.com/s0up4200)) - [`3909aa1`](autobrr/qui@3909aa1): docs: update docs/features/automations.md ([#​1447](autobrr/qui#1447)) ([@​s0up4200](https://github.com/s0up4200)) - [`5dc57ca`](autobrr/qui@5dc57ca): docs: update intro.md ([#​1453](autobrr/qui#1453)) ([@​s0up4200](https://github.com/s0up4200)) - [`5d9e986`](autobrr/qui@5d9e986): perf(web): memoize useDateTimeFormatters ([#​1403](autobrr/qui#1403)) ([@​jabloink](https://github.com/jabloink)) **Full Changelog**: <autobrr/qui@v1.13.1...v1.14.0> #### Docker images - `docker pull ghcr.io/autobrr/qui:v1.14.0` - `docker pull ghcr.io/autobrr/qui:latest` #### What to do next? - Join our [Discord server](https://discord.autobrr.com/qui) Thank you for using qui! </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNS43IiwidXBkYXRlZEluVmVyIjoiNDMuMjUuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=--> Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/4154 Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net> Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Adds dry run toggle and logging for workflow automations.
Closes #1393
Summary by CodeRabbit
New Features
UI
Import/Export
Database
Types
Tests