Sandstorm username races. - #2345: the Card Title Filter is now SAVED by every board trigger builder (a trigger doc missing the field could never satisfy the matcher's $in - those rules fired for nothing), SHOWN in the rule details, and ENFORCED for archive/restore triggers by resolving the title from the card when the activity lacks it; legacy field-less triggers keep matching; the filter popup prefills its current value. - #2674: rules created via POST/PUT /api/boards/:boardId/rules never fired - missing matching fields are now normalized to '*' wildcards and trigger/action types validated; the addMember/removeMember rule actions no longer crash on unresolvable usernames or member-less cards and their writes are awaited. New docs/API/Rules.md (endpoints, matching fields, wildcard semantics, the exact two-rule add-on-move-to/remove-on-move-from workflow), linked from REST-API.md; api.py help now lists the rule commands with the same example. - #574: Sandstorm unique-username generation probes case-insensitively (anchored, regex-escaped) and retries the next number when a concurrent insert wins the unique-index race, instead of aborting the login hook with E11000. Tests: tests/rulesCardTitleFilter.test.cjs (12), tests/rulesApiTriggerNormalize.test.cjs (19), tests/sandstormUsername.test.cjs (11) - positive and negative cases. Thanks to InfoSec812, sfahrenholz, mitar and xet7.
7.2 KiB
Rules (Board Automation / IFTTT) REST API
Board automation rules ("IFTTT rules") link a trigger (an event on the board) to an action (what happens to the card/board). The REST API embeds the full trigger and action inline, so a rule is self-contained.
This API makes the workflow of issue #2674 scriptable: add a user to a card when it is moved to a list, and remove the user again when the card is moved away from that list.
Summary
| HTTP Method | Url | Short Description |
|---|---|---|
GET |
/api/boards/:boardId/rules |
List all automation rules of a board |
GET |
/api/boards/:boardId/rules/:ruleId |
Get one rule (with its trigger and action) |
POST |
/api/boards/:boardId/rules |
Add an automation rule |
PUT |
/api/boards/:boardId/rules/:ruleId |
Edit a rule's title, trigger and/or action |
DELETE |
/api/boards/:boardId/rules/:ruleId |
Remove a rule (and its trigger and action) |
All endpoints require a Bearer token; write endpoints require board write
access (BoardAdmin).
Trigger matching and the * wildcard
The rule engine matches an incoming board activity against every matching
field of the trigger's activityType (for example moveCard matches on
boardId, listName, oldListName, userId, swimlaneName, cardTitle).
A field set to '*' matches anything.
You only need to supply the fields you want to constrain: any matching field
missing from the trigger you POST/PUT is stored as the '*' wildcard
automatically. (Before this normalization existed, an API-created trigger
that omitted a field such as userId could never match any activity, so the
rule silently never fired — the bug reported in issue #2674.)
Trigger activityType values
activityType |
fires when | matching fields |
|---|---|---|
createCard |
a card is created | boardId, listName, userId, swimlaneName, cardTitle |
moveCard |
a card is moved to/from a list or swimlane | boardId, listName (destination), oldListName (source), userId, swimlaneName, cardTitle |
archivedCard / restoredCard |
a card is archived / unarchived | boardId, userId, cardTitle |
joinMember / unjoinMember |
a member is added to / removed from a card | boardId, username, userId |
addChecklist / removeChecklist / completeChecklist / uncompleteChecklist |
checklist events | boardId, checklistName, userId |
addedChecklistItem / removedChecklistItem / checkedItem / uncheckedItem |
checklist item events | boardId, checklistItemName, userId |
addAttachment / deleteAttachment |
attachment events | boardId, userId |
addedLabel / removedLabel |
label events | boardId, labelId, userId |
scheduledTrigger |
time-based (due/aging/recurring); see scheduleKind, days, atTime |
evaluated by the scheduler, not by activities |
For a card moved to a list, constrain listName (the destination list's
title). For a card moved away from a list, constrain oldListName (the
source list's title) and leave listName as '*'.
Action actionType values
moveCardToTop, moveCardToBottom, sendEmail, setDate, updateDate,
removeDate, setDateRelative, archive, unarchive, setColor,
addLabel, removeLabel, addMember, removeMember, checkAll,
uncheckAll, checkItem, uncheckItem, addChecklist, removeChecklist,
addChecklistWithItems, addSwimlane, createCard, linkCard,
markCardComplete, markCardIncomplete, sortList, moveAllCardsInList.
The member actions take a username:
{"actionType": "addMember", "username": "someuser"}— add the user to the card{"actionType": "removeMember", "username": "someuser"}— remove the user from the card{"actionType": "removeMember", "username": "*"}— remove all members from the card
List rules
curl -H "Authorization: Bearer TOKEN" \
-X GET \
http://localhost:3000/api/boards/BOARDID/rules
Get one rule
curl -H "Authorization: Bearer TOKEN" \
-X GET \
http://localhost:3000/api/boards/BOARDID/rules/RULEID
Result example:
{
"_id": "8gDW2AtwbpNbqSyEw",
"title": "Remove member when moved away from Doing",
"trigger": {
"activityType": "moveCard",
"listName": "*",
"oldListName": "Doing",
"swimlaneName": "*",
"cardTitle": "*",
"userId": "*"
},
"action": {
"actionType": "removeMember",
"username": "someuser"
}
}
Add a rule
curl -H "Authorization: Bearer TOKEN" \
-H "Content-type: application/json" \
-X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{
"title": "Add member when moved to Doing",
"trigger": { "activityType": "moveCard", "listName": "Doing" },
"action": { "actionType": "addMember", "username": "someuser" }
}'
Full example: issue #2674 workflow
Add the user when a card is moved to the list "Doing", remove the user again when the card is moved away from "Doing":
# Rule 1: card moved TO "Doing" -> add member
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{ "title": "Add member on move to Doing",
"trigger": { "activityType": "moveCard", "listName": "Doing" },
"action": { "actionType": "addMember", "username": "someuser" } }'
# Rule 2: card moved AWAY FROM "Doing" -> remove member
curl -H "Authorization: Bearer TOKEN" -H "Content-type: application/json" -X POST \
http://localhost:3000/api/boards/BOARDID/rules \
-d '{ "title": "Remove member on move from Doing",
"trigger": { "activityType": "moveCard", "oldListName": "Doing" },
"action": { "actionType": "removeMember", "username": "someuser" } }'
The omitted trigger fields (listName in rule 2, oldListName in rule 1,
plus userId, swimlaneName, cardTitle) are stored as '*' wildcards.
Edit a rule
Any of title, trigger and action may be supplied; a supplied trigger or
action document replaces the stored fields. When the supplied trigger includes
an activityType, its missing matching fields are defaulted to '*' too.
curl -H "Authorization: Bearer TOKEN" \
-H "Content-type: application/json" \
-X PUT \
http://localhost:3000/api/boards/BOARDID/rules/RULEID \
-d '{ "action": { "actionType": "removeMember", "username": "*" } }'
Delete a rule
Removes the rule together with its trigger and action documents.
curl -H "Authorization: Bearer TOKEN" \
-X DELETE \
http://localhost:3000/api/boards/BOARDID/rules/RULEID
Python CLI (api.py)
The bundled api.py exposes the same capability:
python3 api.py listrules BOARDID
python3 api.py getrule BOARDID RULEID
python3 api.py addrule BOARDID 'TITLE' 'TRIGGER_JSON' 'ACTION_JSON'
python3 api.py editrule BOARDID RULEID 'PATCH_JSON'
python3 api.py removerule BOARDID RULEID
# Issue #2674: remove a user from the card when it is moved away from a list
python3 api.py addrule BOARDID 'Remove member on move from Doing' \
'{"activityType":"moveCard","oldListName":"Doing"}' \
'{"actionType":"removeMember","username":"someuser"}'