0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor(console): display webhook test result on details page (#4453)

This commit is contained in:
Xiao Yijun 2023-09-11 10:05:19 +08:00 committed by GitHub
parent c5fcb017ca
commit d1b92e99aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 381 additions and 62 deletions

View file

@ -6,7 +6,8 @@ export type StorageType =
| 'appearance_mode'
| 'linking_social_connector'
| 'checkout_session'
| 'redirect_after_sign_in';
| 'redirect_after_sign_in'
| 'webhook_test_result';
export const getStorageKey = <T extends StorageType>(forType: T) =>
`logto:admin_console:${forType}` as const;
@ -17,4 +18,5 @@ export const storageKeys = Object.freeze({
checkoutSession: getStorageKey('checkout_session'),
/** The react-router redirect location after sign in. The value should be a stringified Location object. */
redirectAfterSignIn: getStorageKey('redirect_after_sign_in'),
webhookTestResult: getStorageKey('webhook_test_result'),
} satisfies Record<CamelCase<StorageType>, string>);

View file

@ -1,6 +1,7 @@
import type { AdminConsoleKey } from '@logto/phrases';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import { forwardRef } from 'react';
import type { ReactNode, Ref } from 'react';
import Info from '@/assets/icons/info.svg';
import Error from '@/assets/icons/toast-error.svg';
@ -24,19 +25,23 @@ type Props = {
className?: string;
};
function InlineNotification({
children,
action,
href,
onClick,
severity = 'info',
variant = 'plain',
hasIcon = true,
isActionLoading = false,
className,
}: Props) {
function InlineNotification(
{
children,
action,
href,
onClick,
severity = 'info',
variant = 'plain',
hasIcon = true,
isActionLoading = false,
className,
}: Props,
ref?: Ref<HTMLDivElement>
) {
return (
<div
ref={ref}
className={classNames(
styles.inlineNotification,
styles[severity],
@ -72,4 +77,4 @@ function InlineNotification({
);
}
export default InlineNotification;
export default forwardRef(InlineNotification);

View file

@ -12,3 +12,7 @@
font: var(--font-body-2);
margin-right: _.unit(3);
}
.result {
margin-top: _.unit(3);
}

View file

@ -1,4 +1,8 @@
import { useState } from 'react';
import { hookTestErrorResponseDataGuard, type RequestErrorBody } from '@logto/schemas';
import { trySafe } from '@silverhand/essentials';
import dayjs from 'dayjs';
import { HTTPError } from 'ky';
import { useRef, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
@ -6,11 +10,13 @@ import { useTranslation } from 'react-i18next';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import FormField from '@/ds-components/FormField';
import InlineNotification from '@/ds-components/InlineNotification';
import useApi from '@/hooks/use-api';
import { type WebhookDetailsFormType } from '@/pages/WebhookDetails/types';
import { webhookDetailsParser } from '@/pages/WebhookDetails/utils';
import * as styles from './index.module.scss';
import useWebhookTestResult from './use-webhook-test-result';
type Props = {
hookId: string;
@ -22,9 +28,12 @@ function TestWebhook({ hookId }: Props) {
getValues,
formState: { isValid },
} = useFormContext<WebhookDetailsFormType>();
const testResultRef = useRef<HTMLDivElement>(null);
const { result, setResult } = useWebhookTestResult();
const [isSendingPayload, setIsSendingPayload] = useState(false);
const api = useApi();
const api = useApi({ hideErrorToast: true });
const sendTestPayload = async () => {
if (isSendingPayload) {
@ -33,13 +42,61 @@ function TestWebhook({ hookId }: Props) {
setIsSendingPayload(true);
const endpointUrl = getValues('url');
const requestTime = Date.now();
try {
const formData = getValues();
const { events, config } = webhookDetailsParser.toRemoteModel(formData);
await api.post(`api/hooks/${hookId}/test`, { json: { events, config } });
toast.success(t('webhook_details.settings.test_payload_sent'));
setResult({
result: 'success',
endpointUrl,
message: t('webhook_details.settings.test_result.test_success'),
requestTime,
});
} catch (error: unknown) {
if (!(error instanceof HTTPError)) {
toast.error(error instanceof Error ? String(error) : t('general.unknown_error'));
return;
}
const { code, data, message } = await error.response.clone().json<RequestErrorBody>();
if (code === 'hook.send_test_payload_failed') {
setResult({
result: 'error',
endpointUrl,
requestTime,
message,
});
return;
}
if (code === 'hook.endpoint_responded_with_error') {
const { responseStatus, responseBody } =
trySafe(() => hookTestErrorResponseDataGuard.parse(data)) ?? {};
setResult({
result: 'error',
endpointUrl,
requestTime,
message,
responseStatus,
responseBody,
});
return;
}
throw error;
} finally {
setIsSendingPayload(false);
// Set timeout to wait for the notification to be rendered
setTimeout(() => {
testResultRef.current?.scrollIntoView({ behavior: 'smooth' });
}, 50);
}
};
@ -59,6 +116,55 @@ function TestWebhook({ hookId }: Props) {
}}
/>
</div>
{result && (
<InlineNotification
ref={testResultRef}
hasIcon
severity={result.result}
action="general.got_it"
className={styles.result}
onClick={() => {
setResult(undefined);
}}
>
<div>
<DynamicT
forKey="webhook_details.settings.test_result.endpoint_url"
interpolation={{ url: result.endpointUrl }}
/>
</div>
{result.message && (
<div>
<DynamicT
forKey="webhook_details.settings.test_result.message"
interpolation={{ message: result.message }}
/>
</div>
)}
{result.responseStatus && (
<div>
<DynamicT
forKey="webhook_details.settings.test_result.response_status"
interpolation={{ status: result.responseStatus }}
/>
</div>
)}
{result.responseBody && (
<div>
<DynamicT
forKey="webhook_details.settings.test_result.response_body"
interpolation={{ body: result.responseBody }}
/>
</div>
)}
<div>
<DynamicT
forKey="webhook_details.settings.test_result.request_time"
interpolation={{ time: dayjs(result.requestTime).format('MM/DD/YYYY, hh:mm:ss A') }}
/>
</div>
</InlineNotification>
)}
</FormField>
);
}

View file

@ -0,0 +1,43 @@
import { conditional, conditionalString } from '@silverhand/essentials';
import { useState } from 'react';
import { z } from 'zod';
import { storageKeys } from '@/consts';
import { safeParseJson } from '@/utils/json';
const webhookTestResultGuard = z.object({
result: z.enum(['success', 'error']),
endpointUrl: z.string(),
requestTime: z.number(),
message: z.string().optional(),
responseStatus: z.number().optional(),
responseBody: z.string().optional(),
});
type WebhookTestResult = z.infer<typeof webhookTestResultGuard>;
const readResult = () => {
const parsedJson = safeParseJson(
conditionalString(sessionStorage.getItem(storageKeys.webhookTestResult))
);
return conditional(parsedJson.success && webhookTestResultGuard.parse(parsedJson.data));
};
const useWebhookTestResult = () => {
const [testResult, setTestResult] = useState<WebhookTestResult | undefined>(readResult());
return {
result: testResult,
setResult: (result?: WebhookTestResult) => {
setTestResult(result);
if (result) {
sessionStorage.setItem(storageKeys.webhookTestResult, JSON.stringify(result));
} else {
sessionStorage.removeItem(storageKeys.webhookTestResult);
}
},
};
};
export default useWebhookTestResult;

View file

@ -5,6 +5,7 @@ import {
LogResult,
userInfoSelectFields,
type HookConfig,
type HookTestErrorResponseData,
} from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { conditional, pick, trySafe } from '@silverhand/essentials';
@ -141,12 +142,17 @@ export const createHookLibrary = (queries: Queries) => {
})
);
} catch (error: unknown) {
/**
* Note: We only care about whether the test payload is sent to the endpoint of the webhook,
* so we don't care about http errors returned by the endpoint.
*/
if (error instanceof HTTPError) {
return;
throw new RequestError(
{
status: 422,
code: 'hook.endpoint_responded_with_error',
},
{
responseStatus: error.response.statusCode,
responseBody: String(error.response.rawBody),
} satisfies HookTestErrorResponseData
);
}
throw new RequestError({

View file

@ -73,13 +73,18 @@ describe('hook testing', () => {
await authedAdminApi.delete(`hooks/${created.id}`);
});
it('should return 204 if the hook endpoint return 500', async () => {
it('should return 422 and contains endpoint response if the hook endpoint return 500', async () => {
const payload = getHookCreationPayload(HookEvent.PostRegister);
const created = await authedAdminApi.post('hooks', { json: payload }).json<Hook>();
const response = await authedAdminApi.post(`hooks/${created.id}/test`, {
json: { events: [HookEvent.PostSignIn], config: { url: responseErrorEndpoint } },
});
expect(response.statusCode).toBe(204);
await expectRejects(
authedAdminApi.post(`hooks/${created.id}/test`, {
json: { events: [HookEvent.PostSignIn], config: { url: responseErrorEndpoint } },
}),
{
code: 'hook.endpoint_responded_with_error',
statusCode: 422,
}
);
// Clean Up
await authedAdminApi.delete(`hooks/${created.id}`);

View file

@ -0,0 +1,11 @@
import { type Page } from 'puppeteer';
export const expectToCreateWebhook = async (page: Page) => {
await expect(page).toClick('div[class$=main] div[class$=headline] > button');
await expect(page).toClick('span[class$=label]', { text: 'Create new account' });
await expect(page).toClick('span[class$=label]', { text: 'Sign in' });
await expect(page).toFill('input[name=name]', 'hook_name');
await expect(page).toFill('input[name=url]', 'https://example.com/webhook');
await expect(page).toClick('button[type=submit]');
await page.waitForSelector('div[class$=header] div[class$=metadata] div[class$=title]');
};

View file

@ -7,20 +7,13 @@ import {
expectToClickDetailsPageOption,
expectModalWithTitle,
expectConfirmModalAndAct,
expectMainPageWithTitle,
} from '#src/ui-helpers/index.js';
import { appendPathname, expectNavigation } from '#src/utils.js';
await page.setViewport({ width: 1280, height: 720 });
import { expectToCreateWebhook } from './helpers.js';
const createWebhookFromWebhooksPage = async () => {
await expect(page).toClick('div[class$=main] div[class$=headline] > button');
await expect(page).toClick('span[class$=label]', { text: 'Create new account' });
await expect(page).toClick('span[class$=label]', { text: 'Sign in' });
await expect(page).toFill('input[name=name]', 'hook_name');
await expect(page).toFill('input[name=url]', 'https://example.com/webhook');
await expect(page).toClick('button[type=submit]');
await page.waitForSelector('div[class$=header] div[class$=metadata] div[class$=title]');
};
await page.setViewport({ width: 1280, height: 720 });
describe('webhooks', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
@ -32,23 +25,19 @@ describe('webhooks', () => {
it('navigates to webhooks page on clicking sidebar menu', async () => {
await expectNavigation(page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href));
await expect(page).toMatchElement(
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
{
text: 'Webhooks',
}
);
await expectMainPageWithTitle(page, 'Webhooks');
});
it('can create a new webhook', async () => {
await page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href);
await createWebhookFromWebhooksPage();
await expectToCreateWebhook(page);
// Go to webhook details page
await expect(page).toMatchElement('div[class$=main] div[class$=metadata] div[class$=title]', {
text: 'hook_name',
});
const hookId = await page.$eval(
'div[class$=main] div[class$=metadata] div[class$=row] div:first-of-type',
(element) => element.textContent
@ -87,7 +76,7 @@ describe('webhooks', () => {
it('can update webhook details', async () => {
await page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href);
await createWebhookFromWebhooksPage();
await expectToCreateWebhook(page);
await expect(page).toFill('input[name=name]', 'hook_name_updated');
await expect(page).toFill('input[name=url]', 'https://example.com/new-webhook');
@ -98,7 +87,7 @@ describe('webhooks', () => {
it('can disable or enable a webhook', async () => {
await page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href);
await createWebhookFromWebhooksPage();
await expectToCreateWebhook(page);
// Disable webhook
await expectToClickDetailsPageOption(page, 'Disable webhook');
@ -127,7 +116,7 @@ describe('webhooks', () => {
it('can regenerate signing key for a webhook', async () => {
await page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href);
await createWebhookFromWebhooksPage();
await expectToCreateWebhook(page);
await expect(page).toClick('button[class$=regenerateButton]');
await expectConfirmModalAndAct(page, {
@ -137,4 +126,15 @@ describe('webhooks', () => {
await waitForToast(page, { text: 'Signing key has been regenerated.' });
});
it('should display an error info if test failed', async () => {
await expect(page).toClick('div[class$=field] button span', { text: 'Send test payload' });
await expect(page).toMatchElement('div[class*=inlineNotification] div[class$=content] div', {
text: /Endpoint URL:/,
timeout: 3000,
});
await expect(page).toClick('div[class*=inlineNotification] button span', { text: 'Got it' });
await page.waitForSelector('div[class*=inlineNotification]', { hidden: true });
});
});

View file

@ -129,3 +129,13 @@ export const expectToOpenNewPage = async (browser: Browser, url: string) => {
await newPage?.close();
};
export const expectMainPageWithTitle = async (page: Page, title: string) => {
await expect(page).toMatchElement(
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
{
text: title,
timeout: 2000,
}
);
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Sie müssen mindestens ein Ereignis angeben.',
send_test_payload_failed: 'Fehler beim Senden des Testpayloads: {{message}}',
endpoint_responded_with_error: 'Endpunkt antwortete mit Fehler.',
};
export default Object.freeze(hook);

View file

@ -46,7 +46,14 @@ const webhook_details = {
test_webhook_description:
'Konfigurieren Sie den Webhook und testen Sie ihn mit Beispieldaten für jede ausgewählte Ereigniskategorie, um eine korrekte Empfangs- und Verarbeitungsfunktion zu überprüfen.',
send_test_payload: 'Testnutzlast senden',
test_payload_sent: 'Die Nutzlast wurde erfolgreich gesendet.',
test_result: {
endpoint_url: 'Endpunkt-URL: {{url}}',
message: 'Nachricht: {{message}}',
response_status: 'Antwortstatus: {{status, number}}',
response_body: 'Antwortkörper: {{body}}',
request_time: 'Anforderungszeit: {{time}}',
test_success: 'Der Webhook-Test zum Endpunkt war erfolgreich.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'You need to provide at least one event.',
send_test_payload_failed: 'Failed to send test payload: {{message}}',
endpoint_responded_with_error: 'Endpoint responded with error.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Configure the webhook, and test it with payload examples for each selected event to verify correct reception and processing.',
send_test_payload: 'Send test payload',
test_payload_sent: 'The payload has been sent successfully.',
test_result: {
endpoint_url: 'Endpoint URL: {{url}}',
message: 'Message: {{message}}',
response_status: 'Response status: {{status, number}}',
response_body: 'Response body: {{body}}',
request_time: 'Request time: {{time}}',
test_success: 'The webhook test to the endpoint was successful.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Necesita proporcionar al menos un evento.',
send_test_payload_failed: 'Error al enviar carga útil de prueba: {{message}}',
endpoint_responded_with_error: 'El punto de enlace respondió con un error.',
};
export default Object.freeze(hook);

View file

@ -46,7 +46,14 @@ const webhook_details = {
test_webhook_description:
'Configure el webhook y pruébelo con ejemplos de carga útil para cada evento seleccionado para verificar la recepción y el procesamiento correcto.',
send_test_payload: 'Enviar carga útil de prueba',
test_payload_sent: 'La carga útil se ha enviado con éxito.',
test_result: {
endpoint_url: 'URL del punto final: {{url}}',
message: 'Mensaje: {{message}}',
response_status: 'Estado de la respuesta: {{status, number}}',
response_body: 'Cuerpo de la respuesta: {{body}}',
request_time: 'Tiempo de solicitud: {{time}}',
test_success: 'La prueba del webhook en el punto final fue exitosa.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Vous devez fournir au moins un événement.',
send_test_payload_failed: "Échec de l'envoi de la charge utile de test : {{message}}",
endpoint_responded_with_error: 'Le point de terminaison a répondu avec une erreur.',
};
export default Object.freeze(hook);

View file

@ -46,7 +46,14 @@ const webhook_details = {
test_webhook_description:
'Configurez le webhook, et testez-le avec des exemples de payload pour chaque événement sélectionné pour vérifier la réception et le traitement corrects.',
send_test_payload: 'Envoyer une charge utile de test',
test_payload_sent: 'La charge utile a été envoyée avec succès.',
test_result: {
endpoint_url: 'URL du point de terminaison : {{url}}',
message: 'Message : {{message}}',
response_status: 'Statut de la réponse : {{status, number}}',
response_body: 'Corps de la réponse : {{body}}',
request_time: 'Temps de la demande : {{time}}',
test_success: 'Le test du webhook vers le point de terminaison a réussi.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'È necessario fornire almeno un evento.',
send_test_payload_failed: 'Impossibile inviare il payload di prova: {{message}}',
endpoint_responded_with_error: 'Il punto di accesso ha risposto con un errore.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Configurare il webhook e testarlo con esempi di payload per ciascun evento selezionato al fine di verificare la corretta ricezione e elaborazione.',
send_test_payload: 'Invia Payload di Test',
test_payload_sent: 'Il payload è stato inviato con successo.',
test_result: {
endpoint_url: 'URL del punto di arrivo: {{url}}',
message: 'Messaggio: {{message}}',
response_status: 'Stato della risposta: {{status, number}}',
response_body: 'Corpo della risposta: {{body}}',
request_time: 'Tempo di richiesta: {{time}}',
test_success: 'Il test del webhook al punto di arrivo è stato eseguito con successo.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: '少なくとも1つのイベントを提供する必要があります。',
send_test_payload_failed: 'テストペイロードの送信に失敗しました:{{message}}',
endpoint_responded_with_error: 'エンドポイントがエラーで応答しました:{{message}}。',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Webhookを設定し、各選択したイベントのペイロード例を使用してテストして、正しい受信と処理を確認してください。',
send_test_payload: 'テストペイロードを送信する',
test_payload_sent: 'ペイロードが正常に送信されました。',
test_result: {
endpoint_url: 'エンドポイントURL{{url}}',
message: 'メッセージ:{{message}}',
response_status: 'レスポンスステータス:{{status, number}}',
response_body: 'レスポンスボディ:{{body}}',
request_time: 'リクエスト時間:{{time}}',
test_success: 'エンドポイントへのWebhookテストは成功しました。',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: '최소한 하나의 이벤트를 제공해야 합니다.',
send_test_payload_failed: '테스트 페이로드 보내기 실패: {{message}}',
endpoint_responded_with_error: '엔드포인트가 오류로 응답했습니다.',
};
export default Object.freeze(hook);

View file

@ -44,7 +44,14 @@ const webhook_details = {
test_webhook_description:
'웹훅을 구성하고 각 선택한 이벤트의 페이로드 예제로 검증하여 올바른 수신 및 처리를 확인하세요.',
send_test_payload: '테스트 페이로드 보내기',
test_payload_sent: '페이로드가 성공적으로 보내졌습니다.',
test_result: {
endpoint_url: '엔드포인트 URL: {{url}}',
message: '메시지: {{message}}',
response_status: '응답 상태: {{status, number}}',
response_body: '응답 본문: {{body}}',
request_time: '요청 시간: {{time}}',
test_success: '엔드포인트로의 웹훅 테스트가 성공했습니다.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Musisz podać przynajmniej jedno zdarzenie.',
send_test_payload_failed: 'Błąd podczas wysyłania testowego ładunku: {{message}}',
endpoint_responded_with_error: 'Punkt końcowy odpowiedział błędem.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Skonfiguruj webhooka i przetestuj go przy użyciu przykładów ładunku dla każdego wybranego zdarzenia, aby sprawdzić poprawne odbieranie i przetwarzanie.',
send_test_payload: 'Wyślij testowy ładunek',
test_payload_sent: 'Ladunek został pomyślnie wysłany.',
test_result: {
endpoint_url: 'URL punktu końcowego: {{url}}',
message: 'Wiadomość: {{message}}',
response_status: 'Status odpowiedzi: {{status, number}}',
response_body: 'Treść odpowiedzi: {{body}}',
request_time: 'Czas żądania: {{time}}',
test_success: 'Test webhooka do punktu końcowego zakończył się powodzeniem.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Você precisa fornecer pelo menos um evento.',
send_test_payload_failed: 'Falha ao enviar uma carga de teste: {{message}}',
endpoint_responded_with_error: 'O ponto de extremidade respondeu com erro.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Configure o webhook e teste-o com exemplos de carga para cada evento selecionado para verificação da recepção e processamento corretos.',
send_test_payload: 'Enviar carga de teste',
test_payload_sent: 'A carga foi enviada com sucesso.',
test_result: {
endpoint_url: 'URL do ponto de extremidade: {{url}}',
message: 'Mensagem: {{message}}',
response_status: 'Status da resposta: {{status, number}}',
response_body: 'Corpo da resposta: {{body}}',
request_time: 'Tempo da solicitação: {{time}}',
test_success: 'O teste de webhook para o ponto de extremidade foi bem-sucedido.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Você precisa fornecer pelo menos um evento.',
send_test_payload_failed: 'Falha ao enviar carga de teste: {{message}}',
endpoint_responded_with_error: 'O ponto de extremidade respondeu com erro.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Configure o webhook e teste-o com exemplos de carga útil para cada evento selecionado para verificar a recepção e o processamento corretos.',
send_test_payload: 'Enviar carga de teste',
test_payload_sent: 'A carga foi enviada com sucesso.',
test_result: {
endpoint_url: 'URL do ponto de extremidade: {{url}}',
message: 'Mensagem: {{message}}',
response_status: 'Estado da resposta: {{status, number}}',
response_body: 'Corpo da resposta: {{body}}',
request_time: 'Tempo da solicitação: {{time}}',
test_success: 'O teste de webhook para o ponto de extremidade foi bem-sucedido.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'Вы должны предоставить как минимум одно событие.',
send_test_payload_failed: 'Не удалось отправить тестовую нагрузку: {{message}}',
endpoint_responded_with_error: 'Конечная точка ответила ошибкой.',
};
export default Object.freeze(hook);

View file

@ -45,7 +45,14 @@ const webhook_details = {
test_webhook_description:
'Настройте вебхук и протестируйте его с примерами нагрузки для каждого выбранного события, чтобы проверить правильный прием и обработку.',
send_test_payload: 'Отправить тестовую нагрузку',
test_payload_sent: 'Нагрузка успешно отправлена.',
test_result: {
endpoint_url: 'URL конечной точки: {{url}}',
message: 'Сообщение: {{message}}',
response_status: 'Статус ответа: {{status, number}}',
response_body: 'Тело ответа: {{body}}',
request_time: 'Время запроса: {{time}}',
test_success: 'Тест вебхука на конечную точку прошел успешно.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: 'En az bir etkinlik sağlamanız gerekiyor.',
send_test_payload_failed: 'Test yükü gönderilemedi: {{message}}',
endpoint_responded_with_error: 'Nokta yanıt verdi hatayla.',
};
export default Object.freeze(hook);

View file

@ -44,7 +44,14 @@ const webhook_details = {
test_webhook_description:
'Webhooku yapılandırın ve her seçilen olay için yük örnekleriyle test ederek doğru alma ve işleme işlemini doğrulayın.',
send_test_payload: 'Test yükünü gönder',
test_payload_sent: 'Yük başarıyla gönderildi.',
test_result: {
endpoint_url: 'Son nokta URL: {{url}}',
message: 'Mesaj: {{message}}',
response_status: 'Yanıt durumu: {{status, number}}',
response_body: 'Yanıt gövdesi: {{body}}',
request_time: 'İstek zamanı: {{time}}',
test_success: 'Son noktaya yapılan webhook testi başarılı oldu.',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: '你需要提供至少一个事件。',
send_test_payload_failed: '无法发送测试内容:{{message}}',
endpoint_responded_with_error: '端点返回了错误。',
};
export default Object.freeze(hook);

View file

@ -42,7 +42,14 @@ const webhook_details = {
test_webhook_description:
'配置 Webhook并使用每个选定事件的负载示例进行测试以验证正确的接收和处理。',
send_test_payload: '发送测试负载',
test_payload_sent: '负载已成功发送。',
test_result: {
endpoint_url: '端点 URL{{url}}',
message: '消息:{{message}}',
response_status: '响应状态:{{status, number}}',
response_body: '响应主体:{{body}}',
request_time: '请求时间:{{time}}',
test_success: '向端点发起的 webhook 测试成功。',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: '你需要至少提供一個事件。',
send_test_payload_failed: '無法發送測試内容:{{message}}',
endpoint_responded_with_error: '端點返回了錯誤。',
};
export default Object.freeze(hook);

View file

@ -42,7 +42,14 @@ const webhook_details = {
test_webhook_description:
'配置 webhook並使用每個選定事件的負載示例進行測試以驗證正確接收和處理。',
send_test_payload: '發送測試負載',
test_payload_sent: '負載已成功發送。',
test_result: {
endpoint_url: '端點URL{{url}}',
message: '消息:{{message}}',
response_status: '響應狀態:{{status, number}}',
response_body: '響應主體:{{body}}',
request_time: '請求時間:{{time}}',
test_success: '向端點發起的 webhook 測試成功。',
},
},
};

View file

@ -1,6 +1,7 @@
const hook = {
missing_events: '你需要至少提供一個事件。',
send_test_payload_failed: '無法发送測試内容:{{message}}',
endpoint_responded_with_error: '端點返回了錯誤。',
};
export default Object.freeze(hook);

View file

@ -42,7 +42,14 @@ const webhook_details = {
test_webhook_description:
'配置 Webhook並使用每個選定事件的有效載荷示例進行測試以驗證正確的接收和處理。',
send_test_payload: '發送測試有效載荷',
test_payload_sent: '有效載荷已成功發送。',
test_result: {
endpoint_url: '端點URL{{url}}',
message: '訊息:{{message}}',
response_status: '回應狀態:{{status, number}}',
response_body: '回應主體:{{body}}',
request_time: '請求時間:{{time}}',
test_success: '向端點發起的 webhook 測試成功。',
},
},
};

View file

@ -28,3 +28,10 @@ export const hookResponseGuard = Hooks.guard.extend({
});
export type HookResponse = z.infer<typeof hookResponseGuard>;
export const hookTestErrorResponseDataGuard = z.object({
responseStatus: z.number(),
responseBody: z.string(),
});
export type HookTestErrorResponseData = z.infer<typeof hookTestErrorResponseDataGuard>;