Give the digest window a way back
Deleting a message that Slack no longer has counts as done, so the window rewinds instead of staying stuck. A reset button clears the mark by hand, and the empty-run notice now says since the last dispatch instead of seven days.
This commit is contained in:
+6
-3
@@ -287,7 +287,7 @@
|
|||||||
"typeInUse": "Diese Art wird noch benutzt. Nimm stattdessen den Haken bei Aktiv heraus.",
|
"typeInUse": "Diese Art wird noch benutzt. Nimm stattdessen den Haken bei Aktiv heraus.",
|
||||||
"slackSaved": "Die Slack-Einstellungen sind gespeichert.",
|
"slackSaved": "Die Slack-Einstellungen sind gespeichert.",
|
||||||
"reportSent": "Der Bericht ist raus.",
|
"reportSent": "Der Bericht ist raus.",
|
||||||
"reportEmpty": "In den letzten sieben Tagen wurde nichts veröffentlicht, deshalb ging keine Nachricht raus.",
|
"reportEmpty": "Seit dem letzten Versand ist nichts dazugekommen, deshalb ging keine Nachricht raus.",
|
||||||
"reportNoWebhook": "Es ist kein Webhook hinterlegt.",
|
"reportNoWebhook": "Es ist kein Webhook hinterlegt.",
|
||||||
"reportRejected": "Slack hat die Nachricht abgelehnt.",
|
"reportRejected": "Slack hat die Nachricht abgelehnt.",
|
||||||
"messageDeleted": "Die Nachricht ist in Slack gelöscht.",
|
"messageDeleted": "Die Nachricht ist in Slack gelöscht.",
|
||||||
@@ -300,7 +300,8 @@
|
|||||||
"runNoChannel": "Es ist kein Kanal hinterlegt.",
|
"runNoChannel": "Es ist kein Kanal hinterlegt.",
|
||||||
"runNotFound": "Im Kanal war keine passende Nachricht zu finden. Braucht der Bot noch das Recht channels:history?",
|
"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}"
|
"slackSaid": "Slack sagt: {detail}",
|
||||||
|
"windowReset": "Der Zeitraum ist zurückgesetzt. Der nächste Bericht umfasst wieder die letzten sieben Tage."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"nameRequired": "Trag einen Namen ein.",
|
"nameRequired": "Trag einen Namen ein.",
|
||||||
@@ -775,7 +776,9 @@
|
|||||||
"pathNone": "Noch kein Weg hinterlegt, es geht nichts raus.",
|
"pathNone": "Noch kein Weg hinterlegt, es geht nichts raus.",
|
||||||
"messageNoToken": "Ohne Bot-Token nicht löschbar",
|
"messageNoToken": "Ohne Bot-Token nicht löschbar",
|
||||||
"lastSent": "Zuletzt verschickt am {when}. Der nächste Bericht beginnt dort und enthält nur, was seitdem erschienen ist.",
|
"lastSent": "Zuletzt verschickt am {when}. Der nächste Bericht beginnt dort und enthält nur, was seitdem erschienen ist.",
|
||||||
"neverSent": "Noch nichts verschickt. Der erste Bericht umfasst die letzten sieben Tage."
|
"neverSent": "Noch nichts verschickt. Der erste Bericht umfasst die letzten sieben Tage.",
|
||||||
|
"resetWindow": "Zeitraum zurücksetzen",
|
||||||
|
"resetting": "Wird zurückgesetzt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
|||||||
+6
-3
@@ -287,7 +287,7 @@
|
|||||||
"typeInUse": "This type is still in use. Clear the active checkbox instead.",
|
"typeInUse": "This type is still in use. Clear the active checkbox instead.",
|
||||||
"slackSaved": "The Slack settings are saved.",
|
"slackSaved": "The Slack settings are saved.",
|
||||||
"reportSent": "The digest is out.",
|
"reportSent": "The digest is out.",
|
||||||
"reportEmpty": "Nothing was published in the last seven days, so no message went out.",
|
"reportEmpty": "Nothing appeared since the last digest, so no message went out.",
|
||||||
"reportNoWebhook": "No webhook is stored.",
|
"reportNoWebhook": "No webhook is stored.",
|
||||||
"reportRejected": "Slack rejected the message.",
|
"reportRejected": "Slack rejected the message.",
|
||||||
"messageDeleted": "The message is deleted in Slack.",
|
"messageDeleted": "The message is deleted in Slack.",
|
||||||
@@ -300,7 +300,8 @@
|
|||||||
"runNoChannel": "No channel is stored.",
|
"runNoChannel": "No channel is stored.",
|
||||||
"runNotFound": "No matching message found in the channel. Does the bot still need channels:history?",
|
"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}"
|
"slackSaid": "Slack says: {detail}",
|
||||||
|
"windowReset": "The window is reset. The next digest covers the last seven days again."
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"nameRequired": "Enter a name.",
|
"nameRequired": "Enter a name.",
|
||||||
@@ -775,7 +776,9 @@
|
|||||||
"pathNone": "No route stored yet, nothing goes out.",
|
"pathNone": "No route stored yet, nothing goes out.",
|
||||||
"messageNoToken": "Not deletable without a bot token",
|
"messageNoToken": "Not deletable without a bot token",
|
||||||
"lastSent": "Last sent on {when}. The next digest starts there and only carries what appeared since.",
|
"lastSent": "Last sent on {when}. The next digest starts there and only carries what appeared since.",
|
||||||
"neverSent": "Nothing sent yet. The first digest covers the last seven days."
|
"neverSent": "Nothing sent yet. The first digest covers the last seven days.",
|
||||||
|
"resetWindow": "Reset the window",
|
||||||
|
"resetting": "Resetting"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
import { useActionState, useState } from 'react'
|
import { useActionState, useState } from 'react'
|
||||||
import { useTranslations } from 'next-intl'
|
import { useTranslations } from 'next-intl'
|
||||||
import { TbSend } from 'react-icons/tb'
|
import { TbRotate2, TbSend } from 'react-icons/tb'
|
||||||
import { AdminField } from './AdminField'
|
import { AdminField } from './AdminField'
|
||||||
import { AdminFormMessage } from './AdminFormMessage'
|
import { AdminFormMessage } from './AdminFormMessage'
|
||||||
import { AdminSubmit } from './AdminSubmit'
|
import { AdminSubmit } from './AdminSubmit'
|
||||||
import { checkboxClass, hintClass, inputClass, labelClass } from './styles'
|
import { checkboxClass, hintClass, inputClass, labelClass } from './styles'
|
||||||
import { saveSlackSettings, sendWeeklyReportNow } from '~/lib/admin-actions'
|
import { resetReportWindow, saveSlackSettings, sendWeeklyReportNow } from '~/lib/admin-actions'
|
||||||
import { emptyAdminFormState } from '~/lib/admin-forms'
|
import { emptyAdminFormState } from '~/lib/admin-forms'
|
||||||
|
|
||||||
export type SlackFormProps = {
|
export type SlackFormProps = {
|
||||||
@@ -22,6 +22,7 @@ export function SlackForm({ hasWebhook, hasBot, channel, fromEnv, includeInterna
|
|||||||
const t = useTranslations('admin.settings')
|
const t = useTranslations('admin.settings')
|
||||||
const [state, formAction] = useActionState(saveSlackSettings, emptyAdminFormState)
|
const [state, formAction] = useActionState(saveSlackSettings, emptyAdminFormState)
|
||||||
const [report, reportAction] = useActionState(sendWeeklyReportNow, emptyAdminFormState)
|
const [report, reportAction] = useActionState(sendWeeklyReportNow, emptyAdminFormState)
|
||||||
|
const [reset, resetAction] = useActionState(resetReportWindow, emptyAdminFormState)
|
||||||
const [replace, setReplace] = useState(!hasWebhook)
|
const [replace, setReplace] = useState(!hasWebhook)
|
||||||
const [replaceToken, setReplaceToken] = useState(!hasBot)
|
const [replaceToken, setReplaceToken] = useState(!hasBot)
|
||||||
const path = hasBot ? 'pathBot' : hasWebhook ? 'pathWebhook' : 'pathNone'
|
const path = hasBot ? 'pathBot' : hasWebhook ? 'pathWebhook' : 'pathNone'
|
||||||
@@ -132,15 +133,23 @@ export function SlackForm({ hasWebhook, hasBot, channel, fromEnv, includeInterna
|
|||||||
<AdminSubmit pendingLabel={t('saving')}>{t('save')}</AdminSubmit>
|
<AdminSubmit pendingLabel={t('saving')}>{t('save')}</AdminSubmit>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form action={reportAction} className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<AdminFormMessage state={report} />
|
<AdminFormMessage state={report} />
|
||||||
|
<AdminFormMessage state={reset} />
|
||||||
<span className="flex flex-wrap items-center gap-4">
|
<span className="flex flex-wrap items-center gap-4">
|
||||||
<AdminSubmit quiet pendingLabel={t('sending')} icon={<TbSend className="size-4" />}>
|
<form action={reportAction}>
|
||||||
{t('sendNow')}
|
<AdminSubmit quiet pendingLabel={t('sending')} icon={<TbSend className="size-4" />}>
|
||||||
</AdminSubmit>
|
{t('sendNow')}
|
||||||
|
</AdminSubmit>
|
||||||
|
</form>
|
||||||
|
<form action={resetAction}>
|
||||||
|
<AdminSubmit quiet pendingLabel={t('resetting')} icon={<TbRotate2 className="size-4" />}>
|
||||||
|
{t('resetWindow')}
|
||||||
|
</AdminSubmit>
|
||||||
|
</form>
|
||||||
<span className={hintClass}>{t('hints.sendNow')}</span>
|
<span className={hintClass}>{t('hints.sendNow')}</span>
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { performSaveBrand } from './admin-brands'
|
|||||||
import { performCreateClient, performRevokeClient } from './admin-clients'
|
import { performCreateClient, performRevokeClient } from './admin-clients'
|
||||||
import { performDeletePostType, performSavePostType } from './admin-post-types'
|
import { performDeletePostType, performSavePostType } from './admin-post-types'
|
||||||
import { performSaveProject } from './admin-projects'
|
import { performSaveProject } from './admin-projects'
|
||||||
import { performDeleteMessage, performSaveSlack, performSendReport } from './admin-settings'
|
import { performDeleteMessage, performResetWindow, performSaveSlack, performSendReport } from './admin-settings'
|
||||||
import { performGrantRole, performRevokeRole, performSetUserAdmin } from './admin-users'
|
import { performGrantRole, performRevokeRole, performSetUserAdmin } from './admin-users'
|
||||||
import type { AdminFormState } from './admin-forms'
|
import type { AdminFormState } from './admin-forms'
|
||||||
import {
|
import {
|
||||||
@@ -168,3 +168,12 @@ export async function deleteSlackMessage(_state: AdminFormState, formData: FormD
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resetReportWindow(_state: AdminFormState): Promise<AdminFormState> {
|
||||||
|
const viewer = await requireAdmin()
|
||||||
|
const result = await performResetWindow(viewer)
|
||||||
|
|
||||||
|
revalidatePath(adminSettingsPath)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -187,3 +187,18 @@ export async function performDeleteMessage(viewer: Viewer, formData: FormData):
|
|||||||
|
|
||||||
return { status: 'ok', message: 'messageDeleted' }
|
return { status: 'ok', message: 'messageDeleted' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function performResetWindow(viewer: Viewer): Promise<AdminFormState> {
|
||||||
|
await clearSetting('slack.lastSentAt')
|
||||||
|
|
||||||
|
await recordAudit({
|
||||||
|
actorId: viewer.id,
|
||||||
|
actorLabel: viewer.email,
|
||||||
|
action: 'settings.reportReset',
|
||||||
|
entity: 'setting',
|
||||||
|
entityId: 'slack',
|
||||||
|
data: {},
|
||||||
|
})
|
||||||
|
|
||||||
|
return { status: 'ok', message: 'windowReset' }
|
||||||
|
}
|
||||||
|
|||||||
+3
-1
@@ -69,10 +69,12 @@ export async function sendToSlack(message: SlackMessage, target: SlackTarget): P
|
|||||||
return { ok: true, sent: { channel: null, messageTs: null } }
|
return { ok: true, sent: { channel: null, messageTs: null } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goneErrors = ['message_not_found', 'already_deleted']
|
||||||
|
|
||||||
export async function deleteFromSlack(token: string, channel: string, messageTs: string): Promise<SlackResult> {
|
export async function deleteFromSlack(token: string, channel: string, messageTs: string): Promise<SlackResult> {
|
||||||
const { ok, data } = await callApi(token, 'chat.delete', { channel, ts: messageTs })
|
const { ok, data } = await callApi(token, 'chat.delete', { channel, ts: messageTs })
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok && !goneErrors.includes(String(data.error))) {
|
||||||
return { ok: false, detail: String(data.error ?? 'unknown_error') }
|
return { ok: false, detail: String(data.error ?? 'unknown_error') }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user