Kontrakt webhooku: objekt jde vybrat a odmitnuti je videt
Parametr spoustece `data` byl deklarovany jako type string a povinny, zatimco odesilatel ho posila jako objekt a v prvni zprave hovoru ho jeste nema. Kazde volani proto skoncilo na 400 a automatizace hodinu nedelala nic. Za tim byly tri veci, kazda sama o sobe malicherna: - rucne pridany parametr byl vychozi povinny, zatimco parametr odvozeny z ukazkoveho tela nepovinny. Dve ruzna vychozi nastaveni pro tutez vec v jednom formulari. Nove je nepovinny i rucne pridany: povinny znamena "odmitni volani" a do toho nema nikdo spadnout omylem - objekt a seznam neslo vybrat. declarableFieldTypes nabizel jen string, number, boolean a date, a TriggerConfig.tsx mel jeste treti kopii toho seznamu. Deklarovat data jako objekt tedy neslo, i kdyz matchesType objekt umi a operatorsByType pro nej ma operatory - odmitnuti nebylo nikde videt. Skoncilo jako console.warn v logu kontejneru: zadna udalost, zadny beh, nic na detailu automatizace Ten treti bod je ten podstatny. Chybu v kontraktu udela ten, kdo ho psal, ale 400 dostane odesilatel - a ten s tim nic nenadela, casto je to cizi sluzba, ktera volani neopakuje. Majitel automatizace se nedozvi nic a v portalu vypada vsechno v poradku. Detail automatizace proto ukazuje poslednich deset volani: cas, jestli proslo nebo ne, a u odmitnutych duvod. Telo se schvalne neuklada, duvod uz rika, co je spatne, a drzet payloady by znamenalo mit v pameti kopie zakaznickych dat. Seznam je v pameti, restart ho zahodi. Incident se z toho nezaklada a upozorneni se neposila: staci radek, implementator se ozve sam. Vzorova automatizace ma data opravene na object a nepovinne. Do navrhu 25 jsou zapsana rozhodnuti z diskuze: "moje tickety" jsou tickety prirazene mne, v helpdesku ty, ktere jsem zalozil ja, a helpdeskove pozadavky vidi lide podle teze hierarchie jako tickety. Sekce 6 popisuje tuhle zmenu. Overeno na bezici instanci s vlastnim DATA_DIR: telo s data jako objektem projde, prvni zprava hovoru s data null projde, telo bez callSid se dal odmita, a vsechna tri jsou videt v seznamu poslednich volani. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
72da07debe
commit
e8bb4d6e54
@@ -170,8 +170,54 @@ export interface Automation {
|
||||
issues: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Jedno volani webhooku, jak dopadlo.
|
||||
*
|
||||
* Odmitnute volani do ted skoncilo jako radek v logu kontejneru, kam se nikdo
|
||||
* nedostane. Odesilatel dostal 400 a vedel o tom, ale ten, kdo kontrakt napsal,
|
||||
* se nedozvedel nic - automatizace svitila zelene a jen do ni nic nechodilo.
|
||||
*
|
||||
* Telo se **schvalne neuklada**. Duvod odmitnuti uz rika, co je spatne
|
||||
* ("Parametr data ma mit typ string"), a drzet payloady by znamenalo mit
|
||||
* v pameti kopie zakaznickych dat bez toho, aby o tom kdokoliv vedel.
|
||||
*/
|
||||
export interface WebhookCall {
|
||||
at: string;
|
||||
/** true = prevzato do fronty, false = odmitnuto pro neplatna data. */
|
||||
ok: boolean;
|
||||
/** Duvody odmitnuti. Prazdne u prevzatych volani. */
|
||||
problems: string[];
|
||||
/** Beh, ktery z volani vznikl. null u odmitnutych a u duplicit. */
|
||||
runId: string | null;
|
||||
}
|
||||
|
||||
/** Kolik poslednich volani se u automatizace drzi. */
|
||||
const MAX_CALLS = 10;
|
||||
|
||||
const calls = new Map<string, WebhookCall[]>();
|
||||
|
||||
/** Zapise, jak dopadlo jedno volani webhooku. Nejnovejsi je prvni. */
|
||||
export function recordWebhookCall(automationId: string, call: WebhookCall): void {
|
||||
const list = calls.get(automationId) ?? [];
|
||||
list.unshift(call);
|
||||
if (list.length > MAX_CALLS) list.length = MAX_CALLS;
|
||||
calls.set(automationId, list);
|
||||
}
|
||||
|
||||
/** Poslednich par volani. Cte se jen pres detail automatizace, ktery hlida firmu. */
|
||||
export function recentWebhookCalls(automationId: string): WebhookCall[] {
|
||||
return calls.get(automationId) ?? [];
|
||||
}
|
||||
|
||||
export interface AutomationDetail extends Automation {
|
||||
flow: AutomationFlow;
|
||||
/**
|
||||
* Poslednich `MAX_CALLS` volani webhooku, nejnovejsi prvni.
|
||||
*
|
||||
* Drzi se v pameti, restart je zahodi. Je to diagnostika posledni hodiny,
|
||||
* ne historie - na tu jsou behy.
|
||||
*/
|
||||
recentCalls: WebhookCall[];
|
||||
/**
|
||||
* Model prichozich dat odvozeny z ukazky u spoustece.
|
||||
*
|
||||
@@ -1001,7 +1047,9 @@ function seedRealAutomations(): void {
|
||||
{ id: 'f_voicebot', name: 'voicebotId', type: 'string', required: true },
|
||||
{ id: 'f_mtjqv4qj_1', name: 'result', type: 'string', required: false, path: 'data.result' },
|
||||
{ id: 'f_mtjqv4zn_2', name: 'rating', type: 'string', required: false, path: 'data.rating' },
|
||||
{ id: 'f_mtjqvws7_3', name: 'data', type: 'string', required: true, path: 'data' },
|
||||
// Objekt a nepovinne: telo ho posila jako strukturu a prvni zprava
|
||||
// hovoru ho jeste nema. Deklarace `string` a povinny odmitala oboji.
|
||||
{ id: 'f_mtjqvws7_3', name: 'data', type: 'object', required: false, path: 'data' },
|
||||
],
|
||||
webhookToken: config.seedWebhookToken || generateWebhookToken(),
|
||||
},
|
||||
@@ -1101,6 +1149,7 @@ function toDetail(stored: StoredAutomation): AutomationDetail {
|
||||
model: stored.flow.trigger?.sample === undefined
|
||||
? []
|
||||
: describeModel(stored.flow.trigger.sample),
|
||||
recentCalls: recentWebhookCalls(stored.id),
|
||||
createdAt: stored.createdAt,
|
||||
updatedAt: stored.updatedAt,
|
||||
};
|
||||
|
||||
+17
-2
@@ -35,8 +35,23 @@ export const fieldTypes: FieldType[] = [
|
||||
'list',
|
||||
];
|
||||
|
||||
/** Typy, ktere si uzivatel muze zvolit u vlastniho parametru spoustece. */
|
||||
export const declarableFieldTypes: FieldType[] = ['string', 'number', 'boolean', 'date'];
|
||||
/**
|
||||
* Typy, ktere si uzivatel muze zvolit u vlastniho parametru spoustece.
|
||||
*
|
||||
* Objekt a seznam tady driv nebyly, protoze podminka se nad nimi zeptat skoro
|
||||
* nema na co. Jenze parametr neni jen podklad pro podminku: kdyz odesilatel
|
||||
* posle `data` jako objekt, musi jit deklarovat objekt, jinak ho kontrakt
|
||||
* odmitne. Bez toho zbyval jediny "spravny" postup - nedeklarovat ho vubec
|
||||
* a sahat na nej cestou, coz nikoho nenapadne.
|
||||
*/
|
||||
export const declarableFieldTypes: FieldType[] = [
|
||||
'string',
|
||||
'number',
|
||||
'boolean',
|
||||
'date',
|
||||
'object',
|
||||
'list',
|
||||
];
|
||||
|
||||
/** Ktere operatory maji smysl pro ktery typ. */
|
||||
export const operatorsByType: Record<FieldType, ConditionOperator[]> = {
|
||||
|
||||
+25
-1
@@ -1,6 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { findByWebhookToken, type TriggerField } from '../data/automationStore.js';
|
||||
import {
|
||||
findByWebhookToken,
|
||||
recordWebhookCall,
|
||||
type TriggerField,
|
||||
} from '../data/automationStore.js';
|
||||
import type { FieldType } from '../data/conditions.js';
|
||||
import { findByIntakeToken } from '../data/tenants.js';
|
||||
import { intakeEvent } from '../data/ticketStore.js';
|
||||
@@ -191,6 +195,18 @@ webhookRouter.post('/:token', (req, res) => {
|
||||
|
||||
if (problems.length > 0) {
|
||||
console.warn(`[webhook] ${automation.id}: neplatna data - ${problems.join(' ')}`);
|
||||
/*
|
||||
* Odmitnuti musi byt videt v portalu, ne jen v logu kontejneru. Chybu
|
||||
* v kontraktu udela ten, kdo ho psal, ale 400 dostane odesilatel - a ten
|
||||
* s tim nic nenadela. Bez tohohle zaznamu se majitel automatizace nedozvi,
|
||||
* ze uz hodinu nic nechodi, protoze v portalu vypada vsechno v poradku.
|
||||
*/
|
||||
recordWebhookCall(automation.id, {
|
||||
at: new Date().toISOString(),
|
||||
ok: false,
|
||||
problems,
|
||||
runId: null,
|
||||
});
|
||||
return res.status(400).json({
|
||||
error: 'validation_error',
|
||||
message: problems[0],
|
||||
@@ -224,6 +240,14 @@ webhookRouter.post('/:token', (req, res) => {
|
||||
console.info(
|
||||
`[webhook] ${automation.id}: prijato, zarazeno jako ${item?.id ?? '(duplicita)'}`,
|
||||
);
|
||||
// I prevzata volani, aby v prehledu bylo videt "tohle proslo, tohle ne"
|
||||
// a ne jen seznam chyb bez meritka.
|
||||
recordWebhookCall(automation.id, {
|
||||
at: new Date().toISOString(),
|
||||
ok: true,
|
||||
problems: [],
|
||||
runId: item?.id ?? null,
|
||||
});
|
||||
// 202: prevzato, zpracuje se. Ne 200, ktera by rikala "hotovo".
|
||||
return res.status(202).json({
|
||||
accepted: true,
|
||||
|
||||
Reference in New Issue
Block a user