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:
@@ -0,0 +1,83 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import {
|
||||
Automation,
|
||||
AutomationsRepository,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
import { Model } from 'mongoose';
|
||||
import {
|
||||
AutomationDoc,
|
||||
AutomationRunDoc,
|
||||
ReadModels,
|
||||
fromDoc,
|
||||
} from './read-model.schemas';
|
||||
|
||||
const DUPLICATE_KEY = 11000;
|
||||
|
||||
@Injectable()
|
||||
export class MongoAutomationsRepository extends AutomationsRepository {
|
||||
constructor(
|
||||
@InjectModel(ReadModels.Automation)
|
||||
private readonly automations: Model<AutomationDoc>,
|
||||
@InjectModel(ReadModels.AutomationRun)
|
||||
private readonly runs: Model<AutomationRunDoc>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async findByProject(projectId: string): Promise<Automation[]> {
|
||||
const docs = await this.automations
|
||||
.find({ projectId })
|
||||
.sort({ createdAt: 1 })
|
||||
.lean<AutomationDoc[]>();
|
||||
return docs.map((doc) => fromDoc(doc) as Automation);
|
||||
}
|
||||
|
||||
async findActive(projectId: string): Promise<Automation[]> {
|
||||
const docs = await this.automations
|
||||
.find({ projectId, active: true })
|
||||
.lean<AutomationDoc[]>();
|
||||
return docs.map((doc) => fromDoc(doc) as Automation);
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Automation | null> {
|
||||
const doc = await this.automations.findById(id).lean<AutomationDoc>();
|
||||
return doc ? (fromDoc(doc) as Automation) : null;
|
||||
}
|
||||
|
||||
async insert({ id, ...rest }: Automation): Promise<void> {
|
||||
await this.automations.create({ _id: id, ...rest });
|
||||
}
|
||||
|
||||
async update(
|
||||
id: string,
|
||||
changes: Partial<Pick<Automation, 'name' | 'active'>>,
|
||||
): Promise<void> {
|
||||
if (Object.keys(changes).length)
|
||||
await this.automations.updateOne({ _id: id }, { $set: changes });
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.automations.deleteOne({ _id: id });
|
||||
}
|
||||
|
||||
async claimRun(ruleId: string, eventKey: string): Promise<boolean> {
|
||||
try {
|
||||
await this.runs.create({ _id: `${ruleId}:${eventKey}`, at: new Date() });
|
||||
return true;
|
||||
} catch (error) {
|
||||
if ((error as { code?: number }).code === DUPLICATE_KEY) return false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async recordRun(id: string, error: string | null): Promise<void> {
|
||||
await this.automations.updateOne(
|
||||
{ _id: id },
|
||||
{
|
||||
$inc: { runs: 1 },
|
||||
$set: { lastRunAt: new Date().toISOString(), lastError: error },
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user