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>
113 lines
2.9 KiB
TypeScript
113 lines
2.9 KiB
TypeScript
import {
|
|
defaultProjectSettings,
|
|
DevLinkKind,
|
|
DevLinkState,
|
|
IssueDevLinkRecordedEvent,
|
|
IssuePriority,
|
|
IssueStatusChangedEvent,
|
|
ProjectWorkflow,
|
|
SYSTEM_ACTOR_ID,
|
|
} from '@task-stream/api/tracker/domain';
|
|
import {
|
|
AutomationActionKind,
|
|
AutomationTriggerKind,
|
|
normalizeRule,
|
|
ruleApplies,
|
|
triggerOf,
|
|
} from './automation-rules';
|
|
|
|
const workflow = new ProjectWorkflow(defaultProjectSettings());
|
|
const bug = {
|
|
type: 'BUG',
|
|
priority: IssuePriority.HIGH,
|
|
area: 'FRONTEND',
|
|
labels: ['ui'],
|
|
};
|
|
|
|
describe('automation rules', () => {
|
|
it('reads triggers from events, merged pull requests included', () => {
|
|
expect(
|
|
triggerOf(
|
|
new IssueStatusChangedEvent('u', 'i1', 'p', 'TODO', 'IN_QA', null),
|
|
),
|
|
).toEqual({
|
|
kind: AutomationTriggerKind.STATUS_CHANGED,
|
|
issueId: 'i1',
|
|
status: 'IN_QA',
|
|
});
|
|
const merged = new IssueDevLinkRecordedEvent(SYSTEM_ACTOR_ID, 'i2', 'p', {
|
|
kind: DevLinkKind.PULL_REQUEST,
|
|
url: 'https://x/pr/1',
|
|
title: 'x',
|
|
ref: '#1',
|
|
state: DevLinkState.MERGED,
|
|
provider: 'github',
|
|
author: null,
|
|
at: '',
|
|
});
|
|
expect(triggerOf(merged)?.kind).toBe(
|
|
AutomationTriggerKind.PULL_REQUEST_MERGED,
|
|
);
|
|
});
|
|
|
|
it('checks the trigger, its status and every condition', () => {
|
|
const rule = {
|
|
trigger: { kind: AutomationTriggerKind.STATUS_CHANGED, status: 'IN_QA' },
|
|
conditions: { types: ['BUG'], label: 'ui' },
|
|
};
|
|
const into = (status: string) => ({
|
|
kind: AutomationTriggerKind.STATUS_CHANGED,
|
|
status,
|
|
});
|
|
expect(ruleApplies(rule, into('IN_QA'), bug)).toBe(true);
|
|
expect(ruleApplies(rule, into('DONE'), bug)).toBe(false);
|
|
expect(ruleApplies(rule, into('IN_QA'), { ...bug, labels: [] })).toBe(
|
|
false,
|
|
);
|
|
expect(ruleApplies(rule, into('IN_QA'), { ...bug, type: 'TASK' })).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it('refuses actions without a usable value', () => {
|
|
const rule = (kind: AutomationActionKind, value: string | null) => ({
|
|
name: 'R',
|
|
trigger: { kind: AutomationTriggerKind.ISSUE_CREATED },
|
|
conditions: {},
|
|
action: { kind, value },
|
|
});
|
|
const member = (id: string) => id === 'u-ana';
|
|
expect(
|
|
normalizeRule(
|
|
rule(AutomationActionKind.ADD_LABEL, ' Triage '),
|
|
workflow,
|
|
member,
|
|
).action,
|
|
).toEqual({
|
|
kind: AutomationActionKind.ADD_LABEL,
|
|
value: 'triage',
|
|
});
|
|
expect(() =>
|
|
normalizeRule(
|
|
rule(AutomationActionKind.SET_STATUS, 'NOPE'),
|
|
workflow,
|
|
member,
|
|
),
|
|
).toThrow();
|
|
expect(() =>
|
|
normalizeRule(
|
|
rule(AutomationActionKind.ASSIGN, 'u-bob'),
|
|
workflow,
|
|
member,
|
|
),
|
|
).toThrow();
|
|
expect(
|
|
normalizeRule(
|
|
rule(AutomationActionKind.ASSIGN_REPORTER, 'x'),
|
|
workflow,
|
|
member,
|
|
).action.value,
|
|
).toBeNull();
|
|
});
|
|
});
|