An automation rule answers three questions: when (the event), in what state (the condition) and what to do (the actions). Every automated change goes into the Journal with the rule's name and revision number: everything that happened by itself carries a signature.
A rule and its scope
A rule belongs to the company and names its own scope: a list of projects, or every project at once. A rule has an owner and an acting account: rights are checked against that account at the moment the rule acts. By default the acting account is the creator; a company administrator can replace it with a service account, and only with a service account. A rule acting as somebody who has left stops working when they do, while a service account outlives users.
Rules can be created by an administrator of at least one project. A rule can be edited by whoever administers every project in its scope; a rule covering all projects can be edited by company administrators only. A rule is visible to whoever sees its whole scope.
Events
| Event | When it happens |
|---|---|
| An issue is created | A new issue appeared. |
| An issue is changed | The issue's fields changed. It can be narrowed to a list of fields. |
| A transition happens | The issue changed status. It can be narrowed by the statuses it comes from and goes to. |
| A comment is added | Somebody commented on an issue. |
| On a schedule | A time from the rule's schedule arrived. |
| Development events | A branch created, a commit linked, a pull request opened, ready for review, approved, changes requested, merged, closed; see GitHub. |
The event describes the change: which fields moved, and from which status to which. The condition describes the present: what the state is now. "The status changed from X" is a setting on the event; "the status equals Y" is a condition.
Conditions
A condition is written in QQL, the same language as search, with the same fields and functions. An empty condition means "on every matching event". The condition runs with the rights of the account the rule acts as: an issue that account cannot see never matches, so a rule cannot touch what its account would not be shown.
A condition with an error does not save: it is parsed when the rule is saved, and the message points at the place in the query.
Actions
Actions come in two kinds, and the difference is worth understanding before it matters:
Run inside the user's own action: assign, set a field, add a label. They are part of the change itself: if such an action cannot run, the firing is rolled back together with the change that set it off, and the user sees it blocked at once, on screen.
Run afterwards: add a comment, transition, notify a user, link issues, log time, call an external address. They run after the change is saved, and their failure never undoes anybody's work: a comment that failed to appear is a comment that failed to appear, not a lost edit.
Values are validated when the rule is saved: a rule that writes a non-existent option into a select field is rejected when it is saved, so it never damages data at its first event. A duration for logging time is parsed then as well: "an hour and a half" is rejected on saving, 1h 30m is accepted.
State and revisions
A rule has two states: enabled and disabled. It is created disabled. The state is switched in the row of the rules list and on the rule's own form, beside its name, and it is saved with everything else: there is no separate publish step.
Saving is publishing. Every save freezes the content into a revision, and execution reads revisions alone: what is saved is what acts.
Revisions are never rewritten. Going back to an old revision is a new save with the old content: the record only grows. Every Journal entry names the revision the rule acted on, and that can be checked for 90 days, the time the Journal keeps an entry, even after the rule is deleted: deletion is soft, and the Journal and the revisions stay readable.
The check
The check is the section's main screen: the rule is run over real issues without writing anything. The same condition parsing, the same rights, the same order of actions; instead of executing anything, the check collects a plan: which issues matched, which actions would have run, what the outbound call would have been, down to the request body with the secret masked. The issues that did not match are shown as well.
A rule that is switched off can be checked: it is the only way to see what a rule will do before it starts. The check is limited, because a run that is rolled back costs what real work costs: up to 50 issues per run, up to 6 runs per minute per user, up to 10 seconds per run.
Schedules
A scheduled rule waits for no event: it names its own selection in QQL, a time, and a repeat: daily, weekly by weekdays, monthly by days of the month.
- The timezone belongs to the rule, not to the company. In a distributed company "every Monday at 9:00" is a different moment for different rules; the company's timezone is only the suggestion offered at creation.
- Missed slots are not caught up. If the product was unavailable at the appointed time, the rule runs once at the next appointed time. Missed runs are not performed after the product comes back: three missed Mondays do not become three runs on Tuesday.
- One run handles up to 500 issues. When the selection is wider, the rule handles 500 and marks how many are left; after three truncated runs in a row the owner gets an email.
- Every issue of the selection is handled in its own transaction: a failure on one issue is recorded and counted, and the rest of the selection carries on.
- If the previous run has not finished, the next slot is skipped with a Journal entry: two runs of the same rule never overlap.
Cascades and their limit
A rule's action can set off other rules: a transition is an "issue transitioned" event. Such a chain is bounded:
- A rule never reacts to its own consequences: within one chain, each rule fires at most once. A loop that closes is cut, with a Journal entry.
- Cascade depth is limited: 3 by default. Past that the chain stops, with a Journal entry.
- The rate is limited: 60 firings per minute per company by default. Past that, firings are blocked, with a Journal entry.
The whole chain carries one correlation id: the Journal shows what set off what, and at which depth.
Outbound calls
A rule can send data outside Quevell. It is built so that "where" and "what" are the company's decisions and not the rule's:
- Allowed addresses. Calls may go only to hosts on the company's list, which a company administrator keeps. An empty list means outbound calls are switched off. Removing an address does not delete the rules that used it: their calls are blocked from then on, with a Journal entry.
- HTTPS only. The address is always assembled as
https://host/path; there is no way to configure a call over plain HTTP. Methods: POST, PUT, PATCH. - Only what is named leaves. The rule names the fields that leave the product:
key,type,status,link,summary,priority, and custom fields by name. "The whole issue" is not an option: nothing leaves by default. - The authorization secret is stored encrypted, is always shown masked in the interface, and is never written to the Journal. It can be replaced; it cannot be seen again.
The request body has a fixed shape, version 1: version, event, sentAt, idempotencyKey, rule (id, name, revision) and issue with exactly the fields named. The limits: 5 seconds per call, 64 KB for the request and for the readable part of the response. The Journal records that the call happened and the response status; the response body is never recorded.
Switching off by itself
A rule whose deferred actions fail one after another switches itself off: after 5 consecutive failures by default. That goes into the Journal, and in the list of rules such a rule is marked as switched off by the product and not by a person.
What to do: open the rule's Journal, find the reason behind the last failures, fix it, and switch the rule on again. Switching it on resets the failure count.
The Journal
The Journal is one feed: firings and rejections together. A collapsed row says three things: what came of the run, which issue it was about, and when. The issue is named by key and title, and the name is a link to it.
A row expands in place and explains one run: the issue, with a link to it and a second link straight to its history; the event that woke the rule; the condition and whether it held; the actions in the rule's own order, each with its result; the time to the second; and the revision. Each outcome is explained in its own terms: a rejection by rights names whose rights and on which action, a broken loop and an exceeded depth limit name the chain, a failed action names which one and what was rolled back, a selection wider than one run names the numbers, and an outbound call names the address and the answer.
A run that did nothing is in the Journal too: the rule looked, the condition did not hold, and that is written down along with the values the condition actually found on the issue. Silence is the hardest thing to explain.
An event concerns one issue, so its row is about that issue. A scheduled run concerns a selection, so it writes one row saying how many issues it looked at; for any single issue, run the check.
The issue's key is written into the row as the row is written. An issue is deleted only together with its project, and the Journal outlives what it describes: such a row keeps the key as text, without a link.
One rule's Journal lives inside the rule and is visible to everybody who sees the rule itself. The company-wide feed is a right of its own: it is granted to users or to groups, and company administrators have it by default. A row names the issue only to a reader who may open it.
Limits
The limits belong to the company and are changed by a company administrator. The defaults:
| Limit | Default |
|---|---|
| Rules per company | 100 |
| Actions per rule | 10 |
| Firings per minute per company | 60 |
| Cascade depth | 3 |
| Switching off after consecutive failures | 5 |
| Issues per check run | 50 |
| Check runs per minute per user | 6 |
| Issues per scheduled run | 500 |
| Time per outbound call | 5 seconds |