v0.2.2
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
131ef2e938 |
Surface dead filters, and name board and stacks in the moved-card mail
Build package / package (push) Successful in 55s
Build package / php-lint (8.2) (push) Successful in 41s
Build package / php-lint (8.3) (push) Successful in 34s
Build package / php-lint (8.4) (push) Successful in 40s
Build package / xml-lint (push) Successful in 11s
Build package / unit-tests (push) Successful in 42s
A filter whose entries have all been deleted from the board was the one failure mode of this app that was completely invisible: the workflow kept running, kept updating last_run and matched nothing, forever. It happens with deleted labels and just as easily when a filtered user loses board access, because Deck's BoardService::deleteAcl() calls assignedUsersMapper->deleteByParticipantOnBoard() and wipes that user's card assignments. Two low-stakes signals, no new column and no migration: - WorkflowRunner::warnAboutDeadFilters() logs a warning on every run. - workflowIssues in PersonalSettings.vue marks the row red. Deliberately no mail and no `enabled = false`. One deleted label out of three is harmless, and even a fully dead filter can be one board edit away from being live again. Both obey the rule the disable path already follows: only positive knowledge. DeckIntegrationService::findFilterOptions() therefore throws instead of degrading to [], unlike listLabels()/listParticipants(), where an empty list only means an empty dropdown. That also fixes an existing false positive of the same family: loadStacksFor() stored [] when the request failed, so a single failed fetch reported "Quell-Stapel und Ziel-Stapel nicht mehr vorhanden" for an untouched board. Failed requests now store null and are read as "unknown". The comparison itself lives in WorkflowRunner::findDeadFilters(), static and side-effect free like cardMatchesFilters(), with unit tests -- notably that an empty filter is never dead, which would otherwise flag every unfiltered workflow. Separately, the moved-card mail now names the board and both stacks. DeckIntegrationService::describeTargets() resolves the titles at most once per workflow run, and only when a card actually moved and the workflow wants a mail; unreadable titles degrade to #<id> rather than costing the user their notification. |
||
|
|
b4057809ed |
Run Deck as the workflow owner, and drop the job interval
Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 42s
Build package / php-lint (8.4) (push) Successful in 37s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 44s
Build package / package (push) Successful in 59s
Workflows were being switched off with "its board no longer exists or is
no longer available to you" for boards that were perfectly intact.
Deck does not read the session for permissions. PermissionService -- the
class behind every check, including the ones inside CardService::reorder()
-- takes the current user as a plain `private ?string $userId`, filled
from the app container's `userId` service (ISession::get('user_id'),
registered shared). Pimple resolves that once per process and caches it,
and ServerContainer caches Deck's app container just as long. In cron the
value is null whenever a Deck-owned job ran earlier in the same pass, and
otherwise the first workflow owner touched -- never the user being
impersonated. null fails every check, so Deck answered NoPermissionException
for an untouched board and findStackIds() read that as "the board is gone".
IUserSession::setUser() never had any effect on this path.
- DeckIntegrationService::beginUserContext()/endUserContext() pin
PermissionService (mandatory), CardService, BoardService and
ActivityManager (best effort) to the workflow owner, and restore them
afterwards. If PermissionService cannot be pinned, the runner skips that
user instead of acting under someone else's permissions.
- findStackIds() now takes the uid as an argument and is assembled from
pieces that cannot answer for the wrong user: BoardMapper and StackMapper
carry no user state, and getPermissions() is handed the uid explicitly.
It no longer goes through StackService::findAll().
- NoPermissionException is no longer treated as "board missing". Unknown
failures throw, which leaves the workflow enabled.
This also fixes the second half of the same defect: card moves silently
failed for every user except the first one processed in a cron pass.
Separately, RunWorkflowsJob is now a plain Job instead of a TimedJob and
runs on every cron pass. The workflow_deck_automation.interval config key
is gone. Time sensitivity is no longer merely declared but structurally
unreachable: JobList::add() leaves the column at its TIME_SENSITIVE
default, and the ratchet in setLastRun() only fires for TimedJob.
|
||
|
|
0eff367268 |
Handle workflows whose board or stack was deleted
Build package / php-lint (8.2) (push) Successful in 49s
Build package / php-lint (8.3) (push) Successful in 45s
Build package / php-lint (8.4) (push) Successful in 44s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 45s
Build package / package (push) Successful in 1m3s
Deleting a board or stack left the workflow row pointing at nothing: Deck keeps the orphaned cards readable until its DeleteCron purges them, so the run kept failing on the move every few minutes, silently and forever. - The controller now rejects boards and stacks that do not exist (or are not the user's) on create and update, so no new broken row can be stored. - The settings list marks affected workflows in red instead of showing a row that looks healthy. - The background job disables such a workflow and mails its owner once. The mail ignores the notifyEmail flag: that one is about moved cards, this is a notice that the automation stopped. It stays a one-off because a disabled workflow is no longer picked up. The check deliberately goes through StackService::findAll() rather than BoardService::getUserBoards(): Deck injects the current user into BoardService as a string frozen at construction, so in the job -- one process, many users, a cached BoardService -- it would answer for the wrong user or for none, and every workflow on the instance would have been disabled. findStackIds() returns null only for a genuinely missing or forbidden board and throws for anything else, so a Deck outage skips the workflow instead of killing it. |
||
|
|
184c39d829 |
Document that the time_sensitive column has to be reset once
Build package / php-lint (8.2) (push) Successful in 50s
Build package / php-lint (8.3) (push) Successful in 36s
Build package / php-lint (8.4) (push) Successful in 40s
Build package / xml-lint (push) Successful in 11s
Build package / unit-tests (push) Successful in 44s
Build package / package (push) Successful in 50s
Shipping the TIME_SENSITIVE change is not enough for instances that already ran the job: getNext() filters on the time_sensitive column of oc_jobs, and JobList::setLastRun() only ever sets it to TIME_INSENSITIVE -- there is no else branch, and JobList::add() writes the column only when inserting, so re-registering an existing job on app update or app:enable leaves the stamp in place. Document the one-off UPDATE (and the occ-only alternative) in the README troubleshooting section and in CLAUDE.md. |
||
|
|
b37a0db123 |
Make the background job time-sensitive so cron actually runs it
Build package / php-lint (8.2) (push) Successful in 48s
Build package / php-lint (8.3) (push) Successful in 45s
Build package / php-lint (8.4) (push) Successful in 47s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 43s
Build package / package (push) Canceled after 0s
The job was registered but never executed: `background-job:list` kept showing last_run = 1970-01-01. It was marked TIME_INSENSITIVE, and OC\Core\Service\CronService::runCli() reads `maintenance_window_start` and calls jobList->getNext($onlyTimeSensitive = true) whenever the current UTC hour is outside [start, start+4] -- so with a maintenance window configured, time-insensitive jobs are skipped for the other 20 hours of the day. A job whose entire purpose is a 5-minute (now configurable down to 60s) reaction time is time-sensitive by definition. Also fix the occ invocation in README.md and CLAUDE.md: background-job:worker takes job classes, not an app id, so the documented `background-job:worker workflow_deck_automation` matched nothing. |
||
|
|
13903f8137 |
Hide inactive boards, keep source and target stack apart, make the interval configurable
Build package / php-lint (8.2) (push) Successful in 46s
Build package / php-lint (8.3) (push) Successful in 40s
Build package / php-lint (8.4) (push) Successful in 35s
Build package / xml-lint (push) Successful in 13s
Build package / unit-tests (push) Successful in 46s
Build package / package (push) Successful in 1m3s
- Boards: BoardService::getUserBoards() defaults to $includeArchived = true,
and that flag also gates the `deleted_at = 0` condition, so archived and
trashed boards showed up in the dropdown. Request the filtered query
instead, with a fallback to the no-arg call if the signature ever changes.
Stacks need nothing: StackMapper::findAll() always filters deleted_at.
- Each stack dropdown now hides whatever the other one holds, so source and
target can no longer be set to the same stack. The save-time check stays
as the backstop for rows stored before this rule.
- RunWorkflowsJob reads its interval from config.php
('workflow_deck_automation.interval', seconds, default 300, clamped to a
60s minimum). TimedJob re-reads the interval on every cron pass, so a
changed value takes effect without any occ command.
|
||
|
|
8f3e3e694a |
Gate the release on the lint and test jobs
Build package / php-lint (8.2) (push) Successful in 47s
Build package / php-lint (8.3) (push) Successful in 36s
Build package / php-lint (8.4) (push) Successful in 44s
Build package / xml-lint (push) Successful in 14s
Build package / unit-tests (push) Successful in 40s
Build package / package (push) Successful in 1m1s
The four workflows all triggered on the same push to main and ran fully independently, so build-main.yml published a latest-main release even when PHPUnit or the linters were red. `needs:` only works between jobs of one workflow, so the checks move into build-main.yml and the package job now depends on them. Two gaps close along the way: the check workflows only ran on main pushes and pull requests, so a v* tag was published entirely unverified, and `npm run build` only ran on main, so no pull request ever exercised the frontend build -- the workflow now runs on pull_request too, with the publish step skipped so the job acts as a build check. Build and publish deliberately stay in a single job; moving the tarball between jobs would require actions/upload-artifact (unusable on this Gitea, see CLAUDE.md) or actions/cache. |
||
|
|
85b42d84c9 |
Fix unusable personal settings form: v8 Vue idioms and Deck relations
Build package / package (push) Successful in 50s
Lint info.xml / xml-lint (push) Successful in 20s
Lint PHP / php-lint (8.2) (push) Successful in 58s
Lint PHP / php-lint (8.3) (push) Successful in 57s
Lint PHP / php-lint (8.4) (push) Successful in 55s
PHPUnit / unit-tests (push) Successful in 1m7s
The settings page rendered, but nothing in it worked: - All NcSelect dropdowns showed "undefined" for every option. In @nextcloud/vue 9 the `label` prop is vue-select's option display *key*, not a caption, so `label="Board"` read `option.Board`. Use `input-label`. - Saving always failed with "Bitte einen Titel angeben". Vue 3 dropped `.sync`; NcTextField and NcCheckboxRadioSwitch bind via `modelValue`, so `:value.sync` / `:checked.sync` never wrote back. Use `v-model`. - NcButton's style prop is now `variant`, `type` is the native button type and `native-type` is gone. `type="tertiary"` rendered `<button type="tertiary">`, which HTML falls back to `submit` for, making the cancel button submit the form. - The user dropdown was always empty. Deck's RelationalEntity replaces resolved relations with a RelationalObject once an entity is enriched, so `$acl->getParticipant()` returns that wrapper and the uid lives in `getPrimaryKey()` -- probing for `getUID()` yielded null. This also broke the background job's assigned-user filter, which shares the extractor. - A board's ACL never contains its owner (a private board has an empty ACL), so participants are now seeded with the owner, group ACL entries are expanded via IGroupManager and display names resolved via IUserManager. - One board appeared twice: getUserBoards() merges own/group/circle boards and includes archived and trashed ones. Deduplicate by id and drop those. Also keep one failing lookup in onBoardChange from taking the other two dropdowns down with it, and document all of the above in CLAUDE.md. |
||
|
|
9a8e4e47f0 |
Use the built-in GITEA_TOKEN instead of a hand-made RELEASE_TOKEN secret
Build package / package (push) Successful in 59s
Lint info.xml / xml-lint (push) Successful in 13s
Lint PHP / php-lint (8.2) (push) Successful in 43s
Lint PHP / php-lint (8.3) (push) Successful in 36s
Lint PHP / php-lint (8.4) (push) Successful in 34s
PHPUnit / unit-tests (push) Successful in 40s
Gitea injects a token into every Actions job as secrets.GITEA_TOKEN, so the manually created PAT was never necessary. Declare permissions: contents: write on the job, which Gitea maps to Code: write (deleting the latest-main tag) and Releases: write (creating the release, uploading the asset) - the full set the publish step needs. Removes a secret that had to be created by hand, could expire, and was a single point of failure the workflow had no fallback for. |
||
|
|
e06e382147 |
Merge release workflow into the main build workflow
Build package / package (push) Successful in 58s
Lint info.xml / xml-lint (push) Successful in 13s
Lint PHP / php-lint (8.2) (push) Successful in 45s
Lint PHP / php-lint (8.3) (push) Successful in 41s
Lint PHP / php-lint (8.4) (push) Successful in 34s
PHPUnit / unit-tests (push) Successful in 42s
build-release.yml and build-main.yml ran identical steps (checkout, node, npm install, npm run build, make appstore) and differed only in where the package landed. Fold the v* tag trigger into build-main.yml and branch on gitea.ref in the publishing step: main keeps the rolling latest-main pre-release, tags get a normal release on the tag that was just pushed. Tag deletion is now guarded - only latest-main is recreated, since deleting a version tag would destroy the ref that triggered the run. A stale release is still removed in both cases so moved tags republish cleanly. Side effects: version builds are published as durable release assets with a stable URL instead of expiring workflow artifacts, which drops the last actions/upload-artifact usage and with it the @v3 pin forced by the GHES restriction; and the setup-php step goes away, since make appstore only shells out to mkdir/tar/rm. |
||
|
|
d2f6640d3d |
Fix blank personal settings page from mismatched bundle names
Build main artifact / package (push) Successful in 1m11s
Lint info.xml / xml-lint (push) Successful in 14s
Lint PHP / php-lint (8.2) (push) Successful in 48s
Lint PHP / php-lint (8.3) (push) Successful in 39s
Lint PHP / php-lint (8.4) (push) Successful in 34s
PHPUnit / unit-tests (push) Successful in 37s
@nextcloud/vite-config prefixes every entry name with the app id, so the entry "workflow-deck-automation-personal-settings" was emitted as js/workflow_deck_automation-workflow-deck-automation-personal-settings.mjs while Util::addScript() was still asking for the unprefixed name. Nextcloud found neither the script nor the style, so only the empty mount div rendered and the settings page stayed blank. Shorten the entry to "personal-settings" and pass the full on-disk basename (APP_ID . '-personal-settings') to addScript()/addStyle(). Document the prefix rule and the intentionally near-empty CSS entry stub in CLAUDE.md. |
||
|
|
c5bf6f8f0d |
Add rolling main-branch build workflow for manual server deploys
Build main artifact / package (push) Successful in 1m2s
Lint info.xml / xml-lint (push) Successful in 15s
Lint PHP / php-lint (8.2) (push) Successful in 46s
Lint PHP / php-lint (8.3) (push) Successful in 45s
Lint PHP / php-lint (8.4) (push) Successful in 37s
PHPUnit / unit-tests (push) Successful in 42s
Publishes the appstore package as a Gitea release asset on every push to main, so a deployable build can be grabbed without cutting a version tag first. |
||
|
|
086c8c06f3 |
Pin actions/upload-artifact back to v3
Lint info.xml / xml-lint (push) Successful in 15s
Lint PHP / php-lint (8.2) (push) Successful in 42s
Lint PHP / php-lint (8.3) (push) Successful in 43s
Lint PHP / php-lint (8.4) (push) Successful in 41s
PHPUnit / unit-tests (push) Successful in 48s
Build release artifact / package (push) Successful in 1m49s
v4+ uses the @actions/artifact v2 backend, which this self-hosted Gitea instance rejects as unsupported since it identifies as GHES to the action's GHES-detection check. checkout/setup-node don't have this issue and stay on v7. |
||
|
|
09d59d936d |
Add missing typescript devDependency to fix vite-plugin-dts crash
Lint info.xml / xml-lint (push) Successful in 12s
Lint PHP / php-lint (8.2) (push) Successful in 45s
Lint PHP / php-lint (8.3) (push) Successful in 39s
Lint PHP / php-lint (8.4) (push) Successful in 42s
PHPUnit / unit-tests (push) Successful in 39s
Build release artifact / package (push) Failing after 1m34s
vite-plugin-dts peer-depends on typescript: "*" but nothing installed it, so it crashed at module-load time before tsconfig.json was ever consulted. The earlier tsconfig.json fix alone was insufficient. |
||
|
|
f530b8e487 |
Track CLAUDE.md, keep only CLAUDE.local.md ignored
CLAUDE.md is now committed project guidance for future Claude Code sessions; CLAUDE.local.md stays gitignored for machine-local notes. |