Thêm automation condition/action mới
Vị trí mã nguồn
Phần tiêu đề “Vị trí mã nguồn”Thư mụcbackend/extensions/helpdesk/src/endpoints/services/automation/
- conditions.ts evaluateCondition (switch theo field → switch theo op) + evaluateConditions (AND)
- actions.ts runAction (switch theo action.type)
- engine.ts load rule active theo trigger, runGuarded (in-flight guard), tăng run_count
Thư mụcadmin/packages/helpdesk/app/components/helpdesk/workflows/
- automation-rule-edit-slideover.vue form rule — embed 2 builder dưới
- condition-builder.vue / action-builder.vue list wrapper + nút Add
- condition-row.vue / action-row.vue field/operator/value picker thật
Thêm một condition mới
Phần tiêu đề “Thêm một condition mới”Condition có shape cố định:
export interface AutomationCondition { field: string; op: string; value: unknown;}Dispatch 2 tầng — switch theo field, mỗi field có switch riêng theo op. Case mẫu (priority):
case 'priority': switch (op) { case 'eq': return convPriority === String(value); case 'neq': return convPriority !== String(value); case 'in': return Array.isArray(value) && (value as string[]).includes(convPriority); case 'not_in': return Array.isArray(value) && !(value as string[]).includes(convPriority); default: return false; }evaluateConditions AND toàn bộ mảng — rule không có điều kiện thì luôn match:
export function evaluateConditions(conditions: AutomationCondition[], ctx: ConditionContext): boolean { if (!conditions.length) return true; return conditions.every((cond) => evaluateCondition(cond, ctx));}Field hiện có: inbox_id, priority, status, content, label_ids, assignee_id, business_hours, channel_type.
-
Thêm
case '<field_moi>':vào switch outer trongevaluateCondition(), viết switchopriêng theo khuônpriorityở trên (copy đúng shapeeq/neq/in/not_in, hoặc kiểu khác nếu field không phải enum — xem casecontentdùng text-match op). -
Wire vào UI — mở
condition-row.vue, thêm entry vàoFIELD_OPTIONS:app/components/helpdesk/workflows/condition-row.vue:13-22 const FIELD_OPTIONS = [{ label: "Inbox", value: "inbox_id" },{ label: "Priority", value: "priority" },{ label: "Status", value: "status" },{ label: "Message content", value: "content" },{ label: "Labels", value: "label_ids" },{ label: "Assignee", value: "assignee_id" },{ label: "Business hours", value: "business_hours" },{ label: "Channel type", value: "channel_type" },// { label: "Tên hiển thị", value: "<field_moi>" },] -
Thêm nhánh vào
opOptions(chọn tập operator phù hợp — enum dùngSELECT_OPS, text dùngTEXT_OPS, giờ làm việc dùngBH_OPS, hoặc khai<FIELD_MOI>_OPSriêng):condition-row.vue:31-43 const SELECT_OPS = [{ label: "is", value: "eq" },{ label: "is not", value: "neq" },{ label: "is one of", value: "in" },{ label: "is not one of", value: "not_in" },]const opOptions = computed(() => {switch (props.modelValue.field) {case "content": return TEXT_OPScase "business_hours": return BH_OPSdefault: return SELECT_OPS}}) -
Nếu field mới cần widget value khác text đơn giản (dropdown, JSON…), thêm nhánh trong
valueTypevà reset default op trongonFieldChange— cả 2 đều nằm trongcondition-row.vue.
Thêm một action mới
Phần tiêu đề “Thêm một action mới”export interface AutomationAction { type: 'assign' | 'label' | 'set_priority' | 'set_status' | 'send_template' | 'reply'; params: Record<string, unknown>;}Case mẫu — ghi thẳng qua ItemsService (assign):
case 'assign': { const agentId = params.agent_id as string | number | undefined; if (agentId != null && agentId !== '') { const convSvc = await createItemsService(ctx, 'hd_conversations', accountability); await convSvc.updateOne(convId, { assignee_id: Number(agentId) }); } break;}Case mẫu — thao tác trực tiếp junction table qua Knex (label, không qua ItemsService vì cần add/remove idempotent):
case 'label': { const labelId = params.label_id as string | number | undefined; const op = (params.op as string) ?? 'add'; if (labelId == null || labelId === '') break; const labelIdNum = Number(labelId); if (op === 'add') { const existing = await ctx.database('hd_conversation_labels') .where({ hd_conversations_id: convId, hd_labels_id: labelIdNum }) .first(); if (!existing) { await ctx.database('hd_conversation_labels') .insert({ hd_conversations_id: convId, hd_labels_id: labelIdNum }); } } else if (op === 'remove') { await ctx.database('hd_conversation_labels') .where({ hd_conversations_id: convId, hd_labels_id: labelIdNum }) .del(); } break;}-
Mở rộng union
AutomationAction['type']với type mới. -
Thêm
case '<type_moi>':vào switch trongrunAction()— dùng khuônassign(ghi quaItemsService, có permission/activity/event) nếu action chỉ update field đơn giản trên conversation; dùng khuônlabel(Knex trực tiếp) nếu action đụng junction table hoặc cần logic idempotent riêng. -
Wire vào UI — thêm entry vào
ACTION_TYPE_OPTIONS:app/components/helpdesk/workflows/action-row.vue:13-20 const ACTION_TYPE_OPTIONS = [{ label: "Assign agent", value: "assign" },{ label: "Add/remove label", value: "label" },{ label: "Set priority", value: "set_priority" },{ label: "Set status", value: "set_status" },{ label: "Send template", value: "send_template" },{ label: "Reply", value: "reply" },// { label: "Tên hiển thị", value: "<type_moi>" },] -
Thêm block
<template v-else-if="modelValue.type === '<type_moi>'">render param picker cần thiết, gọipatchParam('<param_key>', $event)cho từng param —param_keyphải khớp đúng tênparams.<key>mà case backend đọc.
Đọc tiếp
Phần tiêu đề “Đọc tiếp”- Automation engine — trigger, load rule, run_count, toàn cảnh engine
- SLA & Snooze — ví dụ trigger
sla_warningchạy qua cùng guard - Scale & vận hành — automation guard per-process, rủi ro multi-instance