U posledniho volani je videt, co presne prislo

Prvni verze seznamu poslednich volani ukazovala cas a duvod odmitnuti, ale ne
data. To je k nicemu: "parametr data ma mit typ text" nerekne, co tedy prislo,
a bez toho zbyva hadat.

Argument, ze se telo nema drzet kvuli zakaznickym datum, navic neobstal.
Tickety si cela prijata tela u udalosti drzi uz davno, takze si to jedno misto
zakazovalo neco, co aplikace jinde bezne dela - a zaplatilo za to celym uzitkem
te funkce.

Radek volani se ted rozklikne na dve veci:

- rozpad po parametrech: jmeno, cesta, co se cekalo a co na te ceste opravdu
  bylo, vcetne nahledu hodnoty. Sklada ho readPayload, protoze jen tam je videt
  kontrakt i telo naraz - pozdeji uz se kontrakt muze zmenit
- cele prijate telo, vcetne klicu, ktere odesilatel posila navic a kontrakt
  o nich nevi. Delsi nez 8 kB se usekne

Telo se do zaznamu zmrazi na retezec, aby se pozdeji nezmenilo pod rukama,
a serializace je v try - cyklicka struktura nesmi shodit prijem webhooku.

Overeno na bezici instanci s vlastnim DATA_DIR, telem s data jako textem misto
objektu a bez povinneho status:

  ok     callSid  | cekame string, povinny | prislo text = CA-B
  CHYBA  status   | cekame string, povinny | prislo neprislo
  CHYBA  data     | cekame object          | prislo text = tohle je text misto objektu

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-09-02 10:46:36 +02:00
co-authored by Claude Opus 5
parent e8bb4d6e54
commit 40875439e4
6 changed files with 250 additions and 35 deletions
+99 -21
View File
@@ -1,4 +1,14 @@
import { AlertCircle, ArrowLeft, Check, Info, Pause, Play, Save, Trash2 } from 'lucide-react';
import {
AlertCircle,
ArrowLeft,
Check,
ChevronRight,
Info,
Pause,
Play,
Save,
Trash2,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { DataState } from '@/components/dashboard/DataState';
@@ -7,6 +17,7 @@ import { StepPicker } from '@/components/dashboard/flow/StepPicker';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { apiFetch } from '@/lib/api';
import { cn } from '@/lib/cn';
import {
countSteps,
createActionStep,
@@ -477,12 +488,15 @@ export default function AutomationDetail() {
*
* Odmitnute volani do ted skoncilo jako radek v logu kontejneru. Odesilatel
* dostal 400 a vedel o tom, ale ten, kdo kontrakt psal, se nedozvedel nic -
* automatizace svitila zelene a jen do ni prestalo chodit. Tohle je to nejmensi,
* co staci: kdyz se implementator ozve, ze mu neco nesedi, je tady videt co.
* automatizace svitila zelene a jen do ni prestalo chodit.
*
* Telo se neuklada, jen duvod odmitnuti. Ten uz rika, co je spatne.
* Radek se rozklikne na **rozpad po parametrech a cele prijate telo**. Samotny
* duvod odmitnuti nestaci: "parametr data ma mit typ text" nerekne, co tedy
* prislo, a bez toho zbyva hadat.
*/
function WebhookCalls({ calls }: { calls: WebhookCall[] }) {
const [openAt, setOpenAt] = useState<string | null>(null);
return (
<div className="glass rounded-card p-5">
<h2 className="font-semibold text-white">Poslední volání</h2>
@@ -493,24 +507,88 @@ function WebhookCalls({ calls }: { calls: WebhookCall[] }) {
která jsme odmítli.
</p>
) : (
<ul className="mt-3 space-y-2 text-sm">
{calls.map((call) => (
<li key={`${call.at}-${call.runId ?? 'x'}`} className="flex gap-2">
{call.ok ? (
<Check className="mt-0.5 size-3.5 shrink-0 text-emerald-400" />
) : (
<AlertCircle className="mt-0.5 size-3.5 shrink-0 text-rose-400" />
)}
<div className="min-w-0">
<span className="text-white/70">{formatDateTime(call.at)}</span>
{call.problems.length > 0 && (
<p className="text-xs leading-relaxed text-rose-300/80">
{call.problems.join(' ')}
</p>
<ul className="mt-3 space-y-1.5 text-sm">
{calls.map((call) => {
const open = openAt === call.at;
return (
<li key={call.at} className="rounded-lg border border-ink-600/50">
<button
type="button"
onClick={() => setOpenAt(open ? null : call.at)}
className="flex w-full items-start gap-2 px-3 py-2 text-left hover:bg-ink-800/40"
>
{call.ok ? (
<Check className="mt-0.5 size-3.5 shrink-0 text-emerald-400" />
) : (
<AlertCircle className="mt-0.5 size-3.5 shrink-0 text-rose-400" />
)}
<span className="min-w-0 flex-1">
<span className="text-white/70">{formatDateTime(call.at)}</span>
{call.problems.length > 0 && (
<span className="block text-xs leading-relaxed text-rose-300/80">
{call.problems.join(' ')}
</span>
)}
</span>
<ChevronRight
className={cn('mt-0.5 size-3.5 shrink-0 text-white/30', open && 'rotate-90')}
/>
</button>
{open && (
<div className="space-y-3 border-t border-ink-600/50 px-3 py-3">
{call.fields.length > 0 && (
<table className="w-full text-xs">
<thead>
<tr className="text-left text-white/35">
<th className="pb-1 font-normal">Parametr</th>
<th className="pb-1 font-normal">Čekáme</th>
<th className="pb-1 font-normal">Přišlo</th>
</tr>
</thead>
<tbody>
{call.fields.map((field) => (
<tr
key={field.name}
className={field.ok ? 'text-white/60' : 'text-rose-300'}
>
<td className="py-0.5 pr-2 align-top font-mono">
{field.name}
{field.path !== field.name && (
<span className="block text-white/30">{field.path}</span>
)}
</td>
<td className="py-0.5 pr-2 align-top">
{field.type}
{field.required && <span className="text-white/30">, povinný</span>}
</td>
<td className="min-w-0 py-0.5 align-top">
{field.got}
{field.preview !== '' && (
<span className="block break-all font-mono text-white/40">
{field.preview}
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
)}
<div>
<p className="text-xs text-white/35">
Přijaté tělo{call.truncated && ' (useknuté)'}
</p>
<pre className="mt-1 max-h-64 overflow-auto rounded-lg bg-ink-950/60 p-2 text-xs text-white/60">
{call.body === '' ? '(prázdné)' : call.body}
</pre>
</div>
</div>
)}
</div>
</li>
))}
</li>
);
})}
</ul>
)}
+17 -1
View File
@@ -609,12 +609,28 @@ export interface AutomationFlow {
steps: FlowStep[];
}
/** Jedno volani webhooku, jak dopadlo. Telo se neuklada, jen duvod odmitnuti. */
/** Jak dopadl jeden deklarovany parametr v jednom volani. */
export interface WebhookCallField {
name: string;
path: string;
required: boolean;
type: string;
/** Co na te ceste opravdu bylo: text, cislo, objekt, seznam, nepreslo. */
got: string;
preview: string;
ok: boolean;
}
/** Jedno volani webhooku, jak dopadlo, vcetne prijateho tela. */
export interface WebhookCall {
at: string;
ok: boolean;
problems: string[];
runId: string | null;
fields: WebhookCallField[];
/** Cele prijate telo jako JSON. */
body: string;
truncated: boolean;
}
export interface AutomationDetail extends Automation {