Report what Slack actually answers

The lookup swallowed Slack's error and the interface guessed at a cause.
It now shows the raw reason, widens the search window to six hours and
falls back to matching the message prefix.
This commit is contained in:
Matthias G
2026-08-03 13:03:32 +02:00
parent 7a10754e70
commit bb5028ee55
5 changed files with 45 additions and 19 deletions
+2 -1
View File
@@ -299,7 +299,8 @@
"runNotSent": "Bei diesem Lauf ging keine Nachricht raus.",
"runNoChannel": "Es ist kein Kanal hinterlegt.",
"runNotFound": "Im Kanal war keine passende Nachricht zu finden. Braucht der Bot noch das Recht channels:history?",
"channelRequired": "Zum Bot-Token gehört ein Kanal, sonst weiß Logbuch nicht, wohin."
"channelRequired": "Zum Bot-Token gehört ein Kanal, sonst weiß Logbuch nicht, wohin.",
"slackSaid": "Slack sagt: {detail}"
},
"errors": {
"nameRequired": "Trag einen Namen ein.",
+2 -1
View File
@@ -299,7 +299,8 @@
"runNotSent": "That run sent no message.",
"runNoChannel": "No channel is stored.",
"runNotFound": "No matching message found in the channel. Does the bot still need channels:history?",
"channelRequired": "A bot token needs a channel, otherwise Logbuch has no target."
"channelRequired": "A bot token needs a channel, otherwise Logbuch has no target.",
"slackSaid": "Slack says: {detail}"
},
"errors": {
"nameRequired": "Enter a name.",
+5 -1
View File
@@ -30,7 +30,11 @@ export function MessageDeleteButton({ runId, hint }: MessageDeleteButtonProps) {
</AdminSubmit>
{hint ? <span className="font-mono text-micro text-ink-3">{hint}</span> : null}
{state.status === 'error' && state.message ? (
<span role="alert" className={errorClass}>{messages(state.message)}</span>
<span role="alert" className={errorClass}>
{state.message === 'slackSaid'
? messages('slackSaid', { detail: state.values?.detail ?? '' })
: messages(state.message)}
</span>
) : null}
</form>
)
+14 -8
View File
@@ -132,21 +132,27 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
return invalid('runNoChannel')
}
const messageTs = run.messageTs ?? await findMessageTs({
token,
channel,
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
around: run.createdAt,
})
let messageTs = run.messageTs
if (!messageTs) {
return invalid('runNotFound')
const lookup = await findMessageTs({
token,
channel,
text: reportText({ from: run.fromAt, to: run.toAt, total: run.total }),
around: run.createdAt,
})
if (!lookup.ok) {
return invalid('slackSaid', { detail: lookup.detail })
}
messageTs = lookup.messageTs
}
const result = await deleteFromSlack(token, channel, messageTs)
if (!result.ok) {
return invalid('runDeleteFailed')
return invalid('slackSaid', { detail: result.detail })
}
await markReportRunDeleted(run.id)
+22 -8
View File
@@ -79,14 +79,18 @@ export async function deleteFromSlack(token: string, channel: string, messageTs:
return { ok: true, sent: { channel, messageTs } }
}
export type LookupResult =
| { ok: true, messageTs: string }
| { ok: false, detail: string }
export async function findMessageTs(args: {
token: string
channel: string
text: string
around: Date
window?: number
}): Promise<string | undefined> {
const window = args.window ?? 30 * 60 * 1000
}): Promise<LookupResult> {
const window = args.window ?? 6 * 60 * 60 * 1000
const oldest = (args.around.getTime() - window) / 1000
const latest = (args.around.getTime() + window) / 1000
@@ -95,15 +99,25 @@ export async function findMessageTs(args: {
oldest: String(oldest),
latest: String(latest),
inclusive: true,
limit: 100,
limit: 200,
})
if (!ok || !Array.isArray(data.messages)) {
return undefined
if (!ok) {
return { ok: false, detail: String(data.error ?? 'unknown_error') }
}
const found = (data.messages as { text?: string, ts?: string }[])
.find(message => typeof message.ts === 'string' && message.text === args.text)
const messages = Array.isArray(data.messages) ? data.messages as { text?: string, ts?: string }[] : []
const exact = messages.find(message => typeof message.ts === 'string' && message.text === args.text)
return found?.ts
if (exact?.ts) {
return { ok: true, messageTs: exact.ts }
}
const loose = messages.find(message => typeof message.ts === 'string' && (message.text ?? '').startsWith('Logbuch,'))
if (loose?.ts) {
return { ok: true, messageTs: loose.ts }
}
return { ok: false, detail: `no_match_in_${messages.length}_messages` }
}