feat: automation rules
Owners and admins can set rules of the kind «when a ticket is created, moves, gets assigned, gets a comment or its pull request merges; if it is of this type, priority, area or label; then move it, assign it, label it, change its priority, comment or put it in the active sprint». Rules act as TaskStream within the workflow, run once per event and trigger no others. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,13 @@ import {
|
||||
} from './sprint.commands';
|
||||
import { IssueTree } from './issue-tree';
|
||||
import { GIT_HANDLERS, RecordGitMentionsCommand } from '../git/git';
|
||||
import { RunAutomationCommand } from '../automations/automations';
|
||||
import { RunAutomationCommandHandler } from '../automations/automations';
|
||||
import {
|
||||
AutomationActionKind,
|
||||
AutomationTriggerKind,
|
||||
} from '../automations/automation-rules';
|
||||
import { Automation } from '../ports/automations.repository';
|
||||
import { GitIntegrationsRepository } from '../ports/git-integrations.repository';
|
||||
import { DevLinkKind } from '@task-stream/api/tracker/domain';
|
||||
import { SetIssueParentCommand } from './set-issue-parent.command';
|
||||
@@ -92,6 +99,7 @@ async function setup() {
|
||||
providers: [
|
||||
...COMMAND_HANDLERS,
|
||||
...GIT_HANDLERS,
|
||||
RunAutomationCommandHandler,
|
||||
...TRACKER_FACTORIES,
|
||||
WriteAccess,
|
||||
IssueTree,
|
||||
@@ -520,6 +528,73 @@ describe('tracker commands (event sourced)', () => {
|
||||
expect(devLinks.map((l) => l.ref)).toEqual(['abc']);
|
||||
});
|
||||
|
||||
it('runs automation actions as TaskStream, within the workflow', async () => {
|
||||
const { run, rehydrator, store } = await setup();
|
||||
const projectId = await projectWithTeam(run);
|
||||
const issue = await run(
|
||||
new CreateIssueCommand('dev', projectId, IssueType.BUG, 'Crash', {}),
|
||||
);
|
||||
const rule = (kind: AutomationActionKind, value: string | null) =>
|
||||
({
|
||||
id: 'r1',
|
||||
projectId,
|
||||
name: 'R',
|
||||
active: true,
|
||||
trigger: { kind: AutomationTriggerKind.ISSUE_CREATED },
|
||||
conditions: {},
|
||||
action: { kind, value },
|
||||
}) as Automation;
|
||||
|
||||
await run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.SET_STATUS, 'TODO'),
|
||||
issue.id,
|
||||
),
|
||||
);
|
||||
await run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.ADD_LABEL, 'triage'),
|
||||
issue.id,
|
||||
),
|
||||
);
|
||||
await run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.ASSIGN_REPORTER, null),
|
||||
issue.id,
|
||||
),
|
||||
);
|
||||
await run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.ADD_COMMENT, 'Gracias'),
|
||||
issue.id,
|
||||
),
|
||||
);
|
||||
const rebuilt = await rehydrator.rehydrate(issue.id, Issue);
|
||||
expect([rebuilt.status, rebuilt.labels, rebuilt.assigneeId]).toEqual([
|
||||
'TODO',
|
||||
['triage'],
|
||||
'dev',
|
||||
]);
|
||||
expect(store.types()).toContain('CommentAddedEvent');
|
||||
// The workflow still decides: TODO cannot jump to DONE.
|
||||
await expect(
|
||||
run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.SET_STATUS, 'DONE'),
|
||||
issue.id,
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(BusinessRuleError);
|
||||
expect(
|
||||
await run(
|
||||
new RunAutomationCommand(
|
||||
rule(AutomationActionKind.ADD_TO_ACTIVE_SPRINT, null),
|
||||
issue.id,
|
||||
),
|
||||
),
|
||||
).toBe('No sprint is under way');
|
||||
});
|
||||
|
||||
it('does not let viewers modify tickets', async () => {
|
||||
const { run } = await setup();
|
||||
const projectId = await projectWithTeam(run);
|
||||
|
||||
Reference in New Issue
Block a user