From a57e09d01fa9c004c888c3fa6c218c2228a87bb7 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 13 May 2024 11:48:13 +0800 Subject: [PATCH 01/11] test(core): add integration tests add integration tests for interaction hooks --- .../src/tests/api/hook/hook.trigger.test.ts | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts diff --git a/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts b/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts new file mode 100644 index 00000000000..0ea20f897c4 --- /dev/null +++ b/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts @@ -0,0 +1,270 @@ +import { createHmac } from 'node:crypto'; +import { type RequestListener } from 'node:http'; + +import { + ConnectorType, + InteractionHookEvent, + LogResult, + SignInIdentifier, + type Hook, + type Log, + type LogContextPayload, + type LogKey, +} from '@logto/schemas'; +import { type Optional } from '@silverhand/essentials'; + +import { deleteUser } from '#src/api/admin-user.js'; +import { authedAdminApi } from '#src/api/api.js'; +import { getWebhookRecentLogs } from '#src/api/logs.js'; +import { + clearConnectorsByTypes, + setEmailConnector, + setSmsConnector, +} from '#src/helpers/connector.js'; +import { getHookCreationPayload } from '#src/helpers/hook.js'; +import { createMockServer } from '#src/helpers/index.js'; +import { registerNewUser, resetPassword, signInWithPassword } from '#src/helpers/interactions.js'; +import { + enableAllPasswordSignInMethods, + enableAllVerificationCodeSignInMethods, +} from '#src/helpers/sign-in-experience.js'; +import { generateNewUser, generateNewUserProfile } from '#src/helpers/user.js'; +import { generatePassword, waitFor } from '#src/utils.js'; + +type HookSecureData = { + signature: string; + payload: string; +}; + +// Note: return hook payload and signature for webhook security testing +const hookServerRequestListener: RequestListener = (request, response) => { + // eslint-disable-next-line @silverhand/fp/no-mutation + response.statusCode = 204; + + const data: Uint8Array[] = []; + request.on('data', (chunk: Uint8Array) => { + // eslint-disable-next-line @silverhand/fp/no-mutating-methods + data.push(chunk); + }); + + request.on('end', () => { + response.writeHead(200, { 'Content-Type': 'application/json' }); + const payload = Buffer.concat(data).toString(); + response.end( + JSON.stringify({ + signature: request.headers['logto-signature-sha-256'] as string, + payload, + } satisfies HookSecureData) + ); + }); +}; + +const assertHookLogError = ({ result, error }: LogContextPayload, errorMessage: string) => + result === LogResult.Error && typeof error === 'string' && error.includes(errorMessage); + +describe('trigger hooks', () => { + const { listen, close } = createMockServer(9999, hookServerRequestListener); + + beforeAll(async () => { + await enableAllPasswordSignInMethods({ + identifiers: [SignInIdentifier.Username], + password: true, + verify: false, + }); + await listen(); + }); + + afterAll(async () => { + await close(); + }); + + it('should trigger sign-in hook and record error when interaction finished', async () => { + const createdHook = await authedAdminApi + .post('hooks', { json: getHookCreationPayload(InteractionHookEvent.PostSignIn) }) + .json(); + const logKey: LogKey = 'TriggerHook.PostSignIn'; + + const { + userProfile: { username, password }, + user, + } = await generateNewUser({ username: true, password: true }); + + await signInWithPassword({ username, password }); + + // Check hook trigger log + const logs = await getWebhookRecentLogs( + createdHook.id, + new URLSearchParams({ logKey, page_size: '100' }) + ); + + const hookLog = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); + expect(hookLog).toBeTruthy(); + + if (hookLog) { + expect( + assertHookLogError(hookLog.payload, 'Failed to parse URL from not_work_url') + ).toBeTruthy(); + } + + // Clean up + await authedAdminApi.delete(`hooks/${createdHook.id}`); + await deleteUser(user.id); + }); + + it('should trigger multiple register hooks and record properly when interaction finished', async () => { + const [hook1, hook2, hook3] = await Promise.all([ + authedAdminApi + .post('hooks', { json: getHookCreationPayload(InteractionHookEvent.PostRegister) }) + .json(), + authedAdminApi + .post('hooks', { + json: getHookCreationPayload(InteractionHookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(), + // Using the old API to create a hook + authedAdminApi + .post('hooks', { + json: { + event: InteractionHookEvent.PostRegister, + config: { url: 'http://localhost:9999', retries: 2 }, + }, + }) + .json(), + ]); + const logKey: LogKey = 'TriggerHook.PostRegister'; + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + type HookRequest = { + body: { + userIp?: string; + } & Record; + }; + + // Check hook trigger log + for (const [hook, expectedResult, expectedError] of [ + [hook1, LogResult.Error, 'Failed to parse URL from not_work_url'], + [hook2, LogResult.Success, undefined], + [hook3, LogResult.Success, undefined], + ] satisfies Array<[Hook, LogResult, Optional]>) { + // eslint-disable-next-line no-await-in-loop + const logs = await getWebhookRecentLogs( + hook.id, + new URLSearchParams({ logKey, page_size: '100' }) + ); + + const log = logs.find(({ payload: { hookId } }) => hookId === hook.id); + + expect(log).toBeTruthy(); + + // Skip the test if the log is not found + if (!log) { + return; + } + + // Assert user ip is in the hook request + expect((log.payload.hookRequest as HookRequest).body.userIp).toBeTruthy(); + + // Assert the log result and error message + expect(log.payload.result).toEqual(expectedResult); + + if (expectedError) { + expect(assertHookLogError(log.payload, expectedError)).toBeTruthy(); + } + } + + // Clean up + await Promise.all([ + authedAdminApi.delete(`hooks/${hook1.id}`), + authedAdminApi.delete(`hooks/${hook2.id}`), + authedAdminApi.delete(`hooks/${hook3.id}`), + ]); + await deleteUser(userId); + }); + + it('should secure webhook payload data successfully', async () => { + const createdHook = await authedAdminApi + .post('hooks', { + json: getHookCreationPayload(InteractionHookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(); + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + const logs = await authedAdminApi + .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) + .json(); + + const log = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); + expect(log).toBeTruthy(); + + const response = log?.payload.response; + expect(response).toBeTruthy(); + + const { + body: { signature, payload }, + } = response as { body: HookSecureData }; + + expect(signature).toBeTruthy(); + expect(payload).toBeTruthy(); + + const calculateSignature = createHmac('sha256', createdHook.signingKey) + .update(payload) + .digest('hex'); + + expect(calculateSignature).toEqual(signature); + + await authedAdminApi.delete(`hooks/${createdHook.id}`); + + await deleteUser(userId); + }); + + it('should trigger reset password hook and record properly when interaction finished', async () => { + await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); + await setEmailConnector(); + await setSmsConnector(); + await enableAllVerificationCodeSignInMethods({ + identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone], + password: true, + verify: true, + }); + // Create a reset password hook + const resetPasswordHook = await authedAdminApi + .post('hooks', { + json: getHookCreationPayload( + InteractionHookEvent.PostResetPassword, + 'http://localhost:9999' + ), + }) + .json(); + const logKey: LogKey = 'TriggerHook.PostResetPassword'; + + const { user, userProfile } = await generateNewUser({ + primaryPhone: true, + primaryEmail: true, + password: true, + }); + // Reset Password by Email + await resetPassword({ email: userProfile.primaryEmail }, generatePassword()); + // Reset Password by Phone + await resetPassword({ phone: userProfile.primaryPhone }, generatePassword()); + // Wait for the hook to be trigged + await waitFor(1000); + + const relatedLogs = await getWebhookRecentLogs( + resetPasswordHook.id, + new URLSearchParams({ logKey, page_size: '100' }) + ); + const succeedLogs = relatedLogs.filter( + ({ payload: { result } }) => result === LogResult.Success + ); + + expect(succeedLogs).toHaveLength(2); + + await authedAdminApi.delete(`hooks/${resetPasswordHook.id}`); + await deleteUser(user.id); + await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); + }); +}); From 164c403e4a53b0fe119188dcd0f760ac4e652698 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 13 May 2024 11:50:33 +0800 Subject: [PATCH 02/11] chore(test): remove legacy test remove legacy test --- .../src/tests/api/hook/hook.trigger.test.ts | 270 ------------------ 1 file changed, 270 deletions(-) delete mode 100644 packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts diff --git a/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts b/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts deleted file mode 100644 index 0ea20f897c4..00000000000 --- a/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { createHmac } from 'node:crypto'; -import { type RequestListener } from 'node:http'; - -import { - ConnectorType, - InteractionHookEvent, - LogResult, - SignInIdentifier, - type Hook, - type Log, - type LogContextPayload, - type LogKey, -} from '@logto/schemas'; -import { type Optional } from '@silverhand/essentials'; - -import { deleteUser } from '#src/api/admin-user.js'; -import { authedAdminApi } from '#src/api/api.js'; -import { getWebhookRecentLogs } from '#src/api/logs.js'; -import { - clearConnectorsByTypes, - setEmailConnector, - setSmsConnector, -} from '#src/helpers/connector.js'; -import { getHookCreationPayload } from '#src/helpers/hook.js'; -import { createMockServer } from '#src/helpers/index.js'; -import { registerNewUser, resetPassword, signInWithPassword } from '#src/helpers/interactions.js'; -import { - enableAllPasswordSignInMethods, - enableAllVerificationCodeSignInMethods, -} from '#src/helpers/sign-in-experience.js'; -import { generateNewUser, generateNewUserProfile } from '#src/helpers/user.js'; -import { generatePassword, waitFor } from '#src/utils.js'; - -type HookSecureData = { - signature: string; - payload: string; -}; - -// Note: return hook payload and signature for webhook security testing -const hookServerRequestListener: RequestListener = (request, response) => { - // eslint-disable-next-line @silverhand/fp/no-mutation - response.statusCode = 204; - - const data: Uint8Array[] = []; - request.on('data', (chunk: Uint8Array) => { - // eslint-disable-next-line @silverhand/fp/no-mutating-methods - data.push(chunk); - }); - - request.on('end', () => { - response.writeHead(200, { 'Content-Type': 'application/json' }); - const payload = Buffer.concat(data).toString(); - response.end( - JSON.stringify({ - signature: request.headers['logto-signature-sha-256'] as string, - payload, - } satisfies HookSecureData) - ); - }); -}; - -const assertHookLogError = ({ result, error }: LogContextPayload, errorMessage: string) => - result === LogResult.Error && typeof error === 'string' && error.includes(errorMessage); - -describe('trigger hooks', () => { - const { listen, close } = createMockServer(9999, hookServerRequestListener); - - beforeAll(async () => { - await enableAllPasswordSignInMethods({ - identifiers: [SignInIdentifier.Username], - password: true, - verify: false, - }); - await listen(); - }); - - afterAll(async () => { - await close(); - }); - - it('should trigger sign-in hook and record error when interaction finished', async () => { - const createdHook = await authedAdminApi - .post('hooks', { json: getHookCreationPayload(InteractionHookEvent.PostSignIn) }) - .json(); - const logKey: LogKey = 'TriggerHook.PostSignIn'; - - const { - userProfile: { username, password }, - user, - } = await generateNewUser({ username: true, password: true }); - - await signInWithPassword({ username, password }); - - // Check hook trigger log - const logs = await getWebhookRecentLogs( - createdHook.id, - new URLSearchParams({ logKey, page_size: '100' }) - ); - - const hookLog = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); - expect(hookLog).toBeTruthy(); - - if (hookLog) { - expect( - assertHookLogError(hookLog.payload, 'Failed to parse URL from not_work_url') - ).toBeTruthy(); - } - - // Clean up - await authedAdminApi.delete(`hooks/${createdHook.id}`); - await deleteUser(user.id); - }); - - it('should trigger multiple register hooks and record properly when interaction finished', async () => { - const [hook1, hook2, hook3] = await Promise.all([ - authedAdminApi - .post('hooks', { json: getHookCreationPayload(InteractionHookEvent.PostRegister) }) - .json(), - authedAdminApi - .post('hooks', { - json: getHookCreationPayload(InteractionHookEvent.PostRegister, 'http://localhost:9999'), - }) - .json(), - // Using the old API to create a hook - authedAdminApi - .post('hooks', { - json: { - event: InteractionHookEvent.PostRegister, - config: { url: 'http://localhost:9999', retries: 2 }, - }, - }) - .json(), - ]); - const logKey: LogKey = 'TriggerHook.PostRegister'; - - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const userId = await registerNewUser(username, password); - - type HookRequest = { - body: { - userIp?: string; - } & Record; - }; - - // Check hook trigger log - for (const [hook, expectedResult, expectedError] of [ - [hook1, LogResult.Error, 'Failed to parse URL from not_work_url'], - [hook2, LogResult.Success, undefined], - [hook3, LogResult.Success, undefined], - ] satisfies Array<[Hook, LogResult, Optional]>) { - // eslint-disable-next-line no-await-in-loop - const logs = await getWebhookRecentLogs( - hook.id, - new URLSearchParams({ logKey, page_size: '100' }) - ); - - const log = logs.find(({ payload: { hookId } }) => hookId === hook.id); - - expect(log).toBeTruthy(); - - // Skip the test if the log is not found - if (!log) { - return; - } - - // Assert user ip is in the hook request - expect((log.payload.hookRequest as HookRequest).body.userIp).toBeTruthy(); - - // Assert the log result and error message - expect(log.payload.result).toEqual(expectedResult); - - if (expectedError) { - expect(assertHookLogError(log.payload, expectedError)).toBeTruthy(); - } - } - - // Clean up - await Promise.all([ - authedAdminApi.delete(`hooks/${hook1.id}`), - authedAdminApi.delete(`hooks/${hook2.id}`), - authedAdminApi.delete(`hooks/${hook3.id}`), - ]); - await deleteUser(userId); - }); - - it('should secure webhook payload data successfully', async () => { - const createdHook = await authedAdminApi - .post('hooks', { - json: getHookCreationPayload(InteractionHookEvent.PostRegister, 'http://localhost:9999'), - }) - .json(); - - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const userId = await registerNewUser(username, password); - - const logs = await authedAdminApi - .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) - .json(); - - const log = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); - expect(log).toBeTruthy(); - - const response = log?.payload.response; - expect(response).toBeTruthy(); - - const { - body: { signature, payload }, - } = response as { body: HookSecureData }; - - expect(signature).toBeTruthy(); - expect(payload).toBeTruthy(); - - const calculateSignature = createHmac('sha256', createdHook.signingKey) - .update(payload) - .digest('hex'); - - expect(calculateSignature).toEqual(signature); - - await authedAdminApi.delete(`hooks/${createdHook.id}`); - - await deleteUser(userId); - }); - - it('should trigger reset password hook and record properly when interaction finished', async () => { - await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); - await setEmailConnector(); - await setSmsConnector(); - await enableAllVerificationCodeSignInMethods({ - identifiers: [SignInIdentifier.Email, SignInIdentifier.Phone], - password: true, - verify: true, - }); - // Create a reset password hook - const resetPasswordHook = await authedAdminApi - .post('hooks', { - json: getHookCreationPayload( - InteractionHookEvent.PostResetPassword, - 'http://localhost:9999' - ), - }) - .json(); - const logKey: LogKey = 'TriggerHook.PostResetPassword'; - - const { user, userProfile } = await generateNewUser({ - primaryPhone: true, - primaryEmail: true, - password: true, - }); - // Reset Password by Email - await resetPassword({ email: userProfile.primaryEmail }, generatePassword()); - // Reset Password by Phone - await resetPassword({ phone: userProfile.primaryPhone }, generatePassword()); - // Wait for the hook to be trigged - await waitFor(1000); - - const relatedLogs = await getWebhookRecentLogs( - resetPasswordHook.id, - new URLSearchParams({ logKey, page_size: '100' }) - ); - const succeedLogs = relatedLogs.filter( - ({ payload: { result } }) => result === LogResult.Success - ); - - expect(succeedLogs).toHaveLength(2); - - await authedAdminApi.delete(`hooks/${resetPasswordHook.id}`); - await deleteUser(user.id); - await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); - }); -}); From b494b4f8a85f9ce88b93cc9deb9e34e40cc125e9 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 13 May 2024 18:37:15 +0800 Subject: [PATCH 03/11] feat(console, phrases): update the supported webhook events update the supported webhook events --- .../src/components/BasicWebhookForm/index.tsx | 67 ++++++++++++------- packages/console/src/consts/webhooks.ts | 54 ++++++++++++++- .../index.module.scss | 14 ++++ .../CategorizedCheckboxGroup/index.tsx | 42 ++++++++++++ .../Checkbox/CheckboxGroup/index.tsx | 6 +- .../de/translation/admin-console/webhooks.ts | 16 +++++ .../en/translation/admin-console/webhooks.ts | 9 +++ .../es/translation/admin-console/webhooks.ts | 16 +++++ .../fr/translation/admin-console/webhooks.ts | 16 +++++ .../it/translation/admin-console/webhooks.ts | 16 +++++ .../ja/translation/admin-console/webhooks.ts | 16 +++++ .../ko/translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../ru/translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../translation/admin-console/webhooks.ts | 16 +++++ .../src/foundations/jsonb-types/hooks.ts | 2 +- 21 files changed, 387 insertions(+), 31 deletions(-) create mode 100644 packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.module.scss create mode 100644 packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.tsx diff --git a/packages/console/src/components/BasicWebhookForm/index.tsx b/packages/console/src/components/BasicWebhookForm/index.tsx index b332732964b..f110c7b852c 100644 --- a/packages/console/src/components/BasicWebhookForm/index.tsx +++ b/packages/console/src/components/BasicWebhookForm/index.tsx @@ -1,20 +1,35 @@ -import { type HookEvent, type Hook, type HookConfig, InteractionHookEvent } from '@logto/schemas'; +import { type Hook, type HookConfig, type HookEvent } from '@logto/schemas'; import { Controller, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; -import { hookEventLabel } from '@/consts/webhooks'; -import { CheckboxGroup } from '@/ds-components/Checkbox'; +import { + dataHookEventsLabel, + interactionHookEvents, + schemaGroupedDataHookEvents, +} from '@/consts/webhooks'; +import CategorizedCheckboxGroup, { + type CheckboxOptionGroup, +} from '@/ds-components/Checkbox/CategorizedCheckboxGroup'; import FormField from '@/ds-components/FormField'; import TextInput from '@/ds-components/TextInput'; import { uriValidator } from '@/utils/validator'; import * as styles from './index.module.scss'; -// TODO: Implement all hook events -const hookEventOptions = Object.values(InteractionHookEvent).map((event) => ({ - title: hookEventLabel[event], - value: event, -})); +const hookEventGroups: Array> = [ + ...schemaGroupedDataHookEvents.map(([schema, events]) => ({ + title: dataHookEventsLabel[schema], + options: events.map((event) => ({ + value: event, + })), + })), + { + title: 'webhooks.schemas.interaction', + options: interactionHookEvents.map((event) => ({ + value: event, + })), + }, +]; export type BasicWebhookFormType = { name: Hook['name']; @@ -32,24 +47,6 @@ function BasicWebhookForm() { return ( <> - -
- {t('webhooks.create_form.events_description')} -
- - value.length === 0 ? t('webhooks.create_form.missing_event_error') : true, - }} - render={({ field: { onChange, value } }) => ( - - )} - /> - {errors.events &&
{errors.events.message}
} -
+ + + value.length === 0 ? t('webhooks.create_form.missing_event_error') : true, + }} + render={({ field: { onChange, value } }) => ( + + )} + /> + {errors.events &&
{errors.events.message}
} +
); } diff --git a/packages/console/src/consts/webhooks.ts b/packages/console/src/consts/webhooks.ts index d3b8cec2bea..caf5f2e3ef2 100644 --- a/packages/console/src/consts/webhooks.ts +++ b/packages/console/src/consts/webhooks.ts @@ -1,5 +1,11 @@ import { type AdminConsoleKey } from '@logto/phrases'; -import { InteractionHookEvent, type LogKey } from '@logto/schemas'; +import { + DataHookSchema, + InteractionHookEvent, + hookEvents, + type DataHookEvent, + type LogKey, +} from '@logto/schemas'; type HookEventLabel = { // TODO: Implement all hook events @@ -12,6 +18,15 @@ export const hookEventLabel = Object.freeze({ [InteractionHookEvent.PostSignIn]: 'webhooks.events.post_sign_in', }) satisfies HookEventLabel; +export const dataHookEventsLabel = Object.freeze({ + [DataHookSchema.User]: 'webhooks.schemas.user', + [DataHookSchema.Organization]: 'webhooks.schemas.organization', + [DataHookSchema.Role]: 'webhooks.schemas.role', + [DataHookSchema.Scope]: 'webhooks.schemas.scope', + [DataHookSchema.OrganizationRole]: 'webhooks.schemas.organization_role', + [DataHookSchema.OrganizationScope]: 'webhooks.schemas.organization_scope', +} satisfies Record); + type HookEventLogKey = { // TODO: Implement all hook events [key in InteractionHookEvent]: LogKey; @@ -22,3 +37,40 @@ export const hookEventLogKey = Object.freeze({ [InteractionHookEvent.PostResetPassword]: 'TriggerHook.PostResetPassword', [InteractionHookEvent.PostSignIn]: 'TriggerHook.PostSignIn', }) satisfies HookEventLogKey; + +const dataHookEvents: DataHookEvent[] = hookEvents.filter( + (event): event is DataHookEvent => !(event in InteractionHookEvent) +); + +const isDataHookSchema = (schema: string): schema is DataHookSchema => schema in DataHookSchema; + +// Group DataHook events by schema +const schemaGroupedDataHookEventsMap = dataHookEvents.reduce>( + (eventGroup, event) => { + const [schema] = event.split('.'); + + if (schema && isDataHookSchema(schema)) { + eventGroup.set(schema, [...(eventGroup.get(schema) ?? []), event]); + } + + return eventGroup; + }, + new Map() +); + +export const interactionHookEvents = Object.values(InteractionHookEvent); + +const hookEventSchemaOrder: { + [key in DataHookSchema]: number; +} = { + [DataHookSchema.User]: 0, + [DataHookSchema.Organization]: 1, + [DataHookSchema.Role]: 2, + [DataHookSchema.OrganizationRole]: 3, + [DataHookSchema.Scope]: 4, + [DataHookSchema.OrganizationScope]: 5, +}; + +export const schemaGroupedDataHookEvents = Array.from(schemaGroupedDataHookEventsMap.entries()) + .slice() + .sort(([schemaA], [schemaB]) => hookEventSchemaOrder[schemaA] - hookEventSchemaOrder[schemaB]); diff --git a/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.module.scss b/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.module.scss new file mode 100644 index 00000000000..a06819af89f --- /dev/null +++ b/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.module.scss @@ -0,0 +1,14 @@ +@use '@/scss/underscore' as _; + +.groupTitle { + font: var(--font-body-2); + color: var(--color-text-secondary); + margin-bottom: _.unit(2); +} + +.groupList { + // max two column + gap: _.unit(5); + display: grid; + grid-template-columns: repeat(2, 1fr); +} diff --git a/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.tsx b/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.tsx new file mode 100644 index 00000000000..1f07557df05 --- /dev/null +++ b/packages/console/src/ds-components/Checkbox/CategorizedCheckboxGroup/index.tsx @@ -0,0 +1,42 @@ +import { type AdminConsoleKey } from '@logto/phrases'; +import classNames from 'classnames'; + +import DynamicT from '@/ds-components/DynamicT'; + +import CheckboxGroup, { type Option } from '../CheckboxGroup'; + +import * as styles from './index.module.scss'; + +export type CheckboxOptionGroup = { + title: AdminConsoleKey; + options: Array>; +}; + +type Props = { + readonly groups: Array>; + readonly value: T[]; + readonly onChange: (value: T[]) => void; + readonly className?: string; +}; + +function CategorizedCheckboxGroup({ + groups, + value: checkedValues, + onChange, + className, +}: Props) { + return ( +
+ {groups.map(({ title, options }) => ( +
+
+ +
+ +
+ ))} +
+ ); +} + +export default CategorizedCheckboxGroup; diff --git a/packages/console/src/ds-components/Checkbox/CheckboxGroup/index.tsx b/packages/console/src/ds-components/Checkbox/CheckboxGroup/index.tsx index 6bbd3e54064..faeb433559a 100644 --- a/packages/console/src/ds-components/Checkbox/CheckboxGroup/index.tsx +++ b/packages/console/src/ds-components/Checkbox/CheckboxGroup/index.tsx @@ -8,8 +8,8 @@ import Checkbox from '../Checkbox'; import * as styles from './index.module.scss'; -type Option = { - title: AdminConsoleKey; +export type Option = { + title?: AdminConsoleKey; tag?: ReactNode; value: T; }; @@ -42,7 +42,7 @@ function CheckboxGroup({ key={value} label={ <> - + {title ? : value} {tag} } diff --git a/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts index 6449feaebb9..4e56f966726 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Anmelden', post_reset_password: 'Passwort zurücksetzen', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Name', events: 'Ereignisse', diff --git a/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts index 7aef5f00040..4416826d8f3 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts @@ -8,6 +8,15 @@ const webhooks = { post_sign_in: 'Sign in', post_reset_password: 'Reset password', }, + schemas: { + interaction: 'User interaction', + user: 'User', + organization: 'Organization', + role: 'Role', + scope: 'Permission', + organization_role: 'Organization role', + organization_scope: 'Organization permission', + }, table: { name: 'Name', events: 'Events', diff --git a/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts index 798f63a34e2..5e846d1ca91 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Iniciar sesión', post_reset_password: 'Restablecer contraseña', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nombre', events: 'Eventos', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts index 37bde2c1a5c..4575ab225cc 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Connectez-vous', post_reset_password: 'Réinitialiser le mot de passe', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nom', events: 'Événements', diff --git a/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts index 6ff9208488c..ad83e7b040d 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Accedi', post_reset_password: 'Reimposta password', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nome', events: 'Eventi', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts index 8359cf3fd59..6b49bfd417e 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: 'サインインする', post_reset_password: 'パスワードをリセットする', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: '名前', events: 'イベント', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts index eb61cd51bf5..59eadd01058 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: '로그인', post_reset_password: '비밀번호 재설정', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: '이름', events: '이벤트', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts index 527e9d519c4..57eff4b5b9e 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Zaloguj się', post_reset_password: 'Zresetuj hasło', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nazwa', events: 'Zdarzenia', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts index f9bcbdfbb54..c2eb6e31828 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Entrar', post_reset_password: 'Redefinir senha', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nome', events: 'Eventos', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts index 2b31db5628a..6e10d9f1712 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: 'Entrar', post_reset_password: 'Redefinir senha', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Nome', events: 'Eventos', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts index 40b98de17df..5071f37d582 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Войти', post_reset_password: 'Сбросить пароль', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Имя', events: 'События', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts index 1aa442df296..0ecdcb09fb5 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts @@ -9,6 +9,22 @@ const webhooks = { post_sign_in: 'Oturum açın', post_reset_password: 'Parolayı sıfırla', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: 'Adı', events: 'Olaylar', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts index fdf993fae52..17a51e62f8d 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: '登录', post_reset_password: '重置密码', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: '名称', events: '事件', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts index e3087e772a3..83bdbacec54 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: '登錄', post_reset_password: '重置密碼', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: '名稱', events: '事件', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts index 3cd34871c61..ceacf885976 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts @@ -8,6 +8,22 @@ const webhooks = { post_sign_in: '登錄', post_reset_password: '重置密碼', }, + schemas: { + /** UNTRANSLATED */ + interaction: 'User interaction', + /** UNTRANSLATED */ + user: 'User', + /** UNTRANSLATED */ + organization: 'Organization', + /** UNTRANSLATED */ + role: 'Role', + /** UNTRANSLATED */ + scope: 'Permission', + /** UNTRANSLATED */ + organization_role: 'Organization role', + /** UNTRANSLATED */ + organization_scope: 'Organization permission', + }, table: { name: '名稱', events: '事件', diff --git a/packages/schemas/src/foundations/jsonb-types/hooks.ts b/packages/schemas/src/foundations/jsonb-types/hooks.ts index 87746a3bff4..3505b810744 100644 --- a/packages/schemas/src/foundations/jsonb-types/hooks.ts +++ b/packages/schemas/src/foundations/jsonb-types/hooks.ts @@ -15,7 +15,7 @@ export enum InteractionHookEvent { } // DataHookEvent -enum DataHookSchema { +export enum DataHookSchema { User = 'User', Role = 'Role', Scope = 'Scope', From 8406d64e7db05a1a3fdaa676a09d67e4a68e5177 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Mon, 13 May 2024 19:15:50 +0800 Subject: [PATCH 04/11] refactor(console): rename webhook and webhook log keys rename webhook and webhook log keys --- packages/console/src/consts/webhooks.ts | 23 ------------------- .../src/pages/AuditLogDetails/index.tsx | 11 ++++----- .../WebhookDetails/WebhookLogs/index.tsx | 20 +++++----------- .../console/src/pages/WebhookDetails/utils.ts | 19 ++++++++++++++- packages/console/src/pages/Webhooks/index.tsx | 12 ++-------- .../de/translation/admin-console/webhooks.ts | 5 ---- .../en/translation/admin-console/webhooks.ts | 5 ---- .../es/translation/admin-console/webhooks.ts | 5 ---- .../fr/translation/admin-console/webhooks.ts | 5 ---- .../it/translation/admin-console/webhooks.ts | 5 ---- .../ja/translation/admin-console/webhooks.ts | 5 ---- .../ko/translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../ru/translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- .../translation/admin-console/webhooks.ts | 5 ---- 20 files changed, 31 insertions(+), 129 deletions(-) diff --git a/packages/console/src/consts/webhooks.ts b/packages/console/src/consts/webhooks.ts index caf5f2e3ef2..0aec6c9ffc6 100644 --- a/packages/console/src/consts/webhooks.ts +++ b/packages/console/src/consts/webhooks.ts @@ -4,20 +4,8 @@ import { InteractionHookEvent, hookEvents, type DataHookEvent, - type LogKey, } from '@logto/schemas'; -type HookEventLabel = { - // TODO: Implement all hook events - [key in InteractionHookEvent]: AdminConsoleKey; -}; - -export const hookEventLabel = Object.freeze({ - [InteractionHookEvent.PostRegister]: 'webhooks.events.post_register', - [InteractionHookEvent.PostResetPassword]: 'webhooks.events.post_reset_password', - [InteractionHookEvent.PostSignIn]: 'webhooks.events.post_sign_in', -}) satisfies HookEventLabel; - export const dataHookEventsLabel = Object.freeze({ [DataHookSchema.User]: 'webhooks.schemas.user', [DataHookSchema.Organization]: 'webhooks.schemas.organization', @@ -27,17 +15,6 @@ export const dataHookEventsLabel = Object.freeze({ [DataHookSchema.OrganizationScope]: 'webhooks.schemas.organization_scope', } satisfies Record); -type HookEventLogKey = { - // TODO: Implement all hook events - [key in InteractionHookEvent]: LogKey; -}; - -export const hookEventLogKey = Object.freeze({ - [InteractionHookEvent.PostRegister]: 'TriggerHook.PostRegister', - [InteractionHookEvent.PostResetPassword]: 'TriggerHook.PostResetPassword', - [InteractionHookEvent.PostSignIn]: 'TriggerHook.PostSignIn', -}) satisfies HookEventLogKey; - const dataHookEvents: DataHookEvent[] = hookEvents.filter( (event): event is DataHookEvent => !(event in InteractionHookEvent) ); diff --git a/packages/console/src/pages/AuditLogDetails/index.tsx b/packages/console/src/pages/AuditLogDetails/index.tsx index 8789187b6f1..a5eaa834fff 100644 --- a/packages/console/src/pages/AuditLogDetails/index.tsx +++ b/packages/console/src/pages/AuditLogDetails/index.tsx @@ -1,4 +1,5 @@ -import type { Application, User, Log, Hook } from '@logto/schemas'; +/* eslint-disable complexity */ +import type { Application, Hook, Log, User } from '@logto/schemas'; import { demoAppApplicationId } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import { useTranslation } from 'react-i18next'; @@ -10,13 +11,13 @@ import DetailsPage from '@/components/DetailsPage'; import PageMeta from '@/components/PageMeta'; import UserName from '@/components/UserName'; import { logEventTitle } from '@/consts/logs'; -import { hookEventLogKey } from '@/consts/webhooks'; import Card from '@/ds-components/Card'; import CodeEditor from '@/ds-components/CodeEditor'; import DangerousRaw from '@/ds-components/DangerousRaw'; import FormField from '@/ds-components/FormField'; import TabNav, { TabNavItem } from '@/ds-components/TabNav'; import type { RequestError } from '@/hooks/use-api'; +import { isWebhookEventLogKey } from '@/pages/WebhookDetails/utils'; import { getUserTitle } from '@/utils/user'; import EventIcon from './components/EventIcon'; @@ -28,9 +29,6 @@ const getAuditLogDetailsRelatedResourceLink = (pathname: string) => const getDetailsTabNavLink = (logId: string, userId?: string) => userId ? `/users/${userId}/logs/${logId}` : `/audit-logs/${logId}`; -const isWebhookEventLog = (key?: string) => - key && Object.values(hookEventLogKey).includes(key); - function AuditLogDetails() { const { appId, userId, hookId, logId } = useParams(); const { pathname } = useLocation(); @@ -70,7 +68,7 @@ function AuditLogDetails() { return null; } - const isWebHookEvent = isWebhookEventLog(data?.key); + const isWebHookEvent = isWebhookEventLogKey(data?.key ?? ''); return ( ({ - title: , - value: hookEventLogKey[event], +const hookLogEventOptions = hookEvents.map((event) => ({ + title: event, + value: buildHookEventLogKey(event), })); function WebhookLogs() { @@ -96,13 +94,7 @@ function WebhookLogs() { title: t('logs.event'), dataIndex: 'event', colSpan: 6, - render: ({ key }) => { - // TODO: Implement all hook events - const event = Object.values(InteractionHookEvent).find( - (event) => hookEventLogKey[event] === key - ); - return conditional(event && t(hookEventLabel[event])) ?? '-'; - }, + render: ({ key }) => getHookEventKey(key), }, { title: t('logs.time'), diff --git a/packages/console/src/pages/WebhookDetails/utils.ts b/packages/console/src/pages/WebhookDetails/utils.ts index 84a134e55f2..237a562b87d 100644 --- a/packages/console/src/pages/WebhookDetails/utils.ts +++ b/packages/console/src/pages/WebhookDetails/utils.ts @@ -1,4 +1,4 @@ -import { type Hook } from '@logto/schemas'; +import { hookEvents, type Hook, type HookEvent, type WebhookLogKey } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import { type WebhookDetailsFormType } from './types'; @@ -47,3 +47,20 @@ export const webhookDetailsParser = { }; }, }; + +export const buildHookEventLogKey = (event: HookEvent): WebhookLogKey => `TriggerHook.${event}`; + +export const isWebhookEventLogKey = (logKey: string): logKey is WebhookLogKey => { + const [prefix, ...events] = logKey.split('.'); + + // eslint-disable-next-line no-restricted-syntax + return prefix === 'TriggerHook' && hookEvents.includes(events.join('.') as HookEvent); +}; + +export const getHookEventKey = (logKey: string) => { + if (!isWebhookEventLogKey(logKey)) { + return ' - '; + } + + return logKey.replace('TriggerHook.', ''); +}; diff --git a/packages/console/src/pages/Webhooks/index.tsx b/packages/console/src/pages/Webhooks/index.tsx index c2a622cd9a1..04f925e78c0 100644 --- a/packages/console/src/pages/Webhooks/index.tsx +++ b/packages/console/src/pages/Webhooks/index.tsx @@ -1,4 +1,4 @@ -import { type Hook, Theme, type HookResponse, type InteractionHookEvent } from '@logto/schemas'; +import { Theme, type Hook, type HookResponse } from '@logto/schemas'; import { conditional } from '@silverhand/essentials'; import { toast } from 'react-hot-toast'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,6 @@ import ItemPreview from '@/components/ItemPreview'; import ListPage from '@/components/ListPage'; import SuccessRate from '@/components/SuccessRate'; import { defaultPageSize } from '@/consts'; -import { hookEventLabel } from '@/consts/webhooks'; import Button from '@/ds-components/Button'; import DynamicT from '@/ds-components/DynamicT'; import TablePlaceholder from '@/ds-components/Table/TablePlaceholder'; @@ -91,14 +90,7 @@ function Webhooks() { colSpan: 6, render: ({ event, events }) => { const eventArray = conditional(events.length > 0 && events) ?? [event]; - return ( - eventArray - // TODO: Implement all hook events - // eslint-disable-next-line unicorn/prefer-native-coercion-functions - .filter((_event): _event is InteractionHookEvent => Boolean(_event)) - .map((_event) => t(hookEventLabel[_event])) - .join(', ') - ); + return eventArray.join(', '); }, }, { diff --git a/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts index 4e56f966726..240bb9bb574 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Erstellen Sie Webhooks, um mühelos Echtzeit-Updates zu bestimmten Ereignissen zu empfangen.', create: 'Webhook erstellen', - events: { - post_register: 'Neuen Account anlegen', - post_sign_in: 'Anmelden', - post_reset_password: 'Passwort zurücksetzen', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts index 4416826d8f3..2b6e5250c7c 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: 'Create webhooks to effortlessly receive real-time updates regarding specific events.', create: 'Create Webhook', - events: { - post_register: 'Create new account', - post_sign_in: 'Sign in', - post_reset_password: 'Reset password', - }, schemas: { interaction: 'User interaction', user: 'User', diff --git a/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts index 5e846d1ca91..4323da5abc6 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Crea webhooks para recibir de manera fácil actualizaciones en tiempo real sobre eventos específicos.', create: 'Crear Webhook', - events: { - post_register: 'Crear nueva cuenta', - post_sign_in: 'Iniciar sesión', - post_reset_password: 'Restablecer contraseña', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts index 4575ab225cc..adc6ce86ae6 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Créez des webhooks pour recevoir sans effort des mises à jour en temps réel concernant des événements spécifiques.', create: 'Créer un webhook', - events: { - post_register: 'Nouveau compte créé', - post_sign_in: 'Connectez-vous', - post_reset_password: 'Réinitialiser le mot de passe', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts index ad83e7b040d..ecc98cdbafc 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Crea webhook per ricevere facilmente aggiornamenti in tempo reale relativi a eventi specifici.', create: 'Crea Webhook', - events: { - post_register: 'Crea nuovo account', - post_sign_in: 'Accedi', - post_reset_password: 'Reimposta password', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts index 6b49bfd417e..38a5767a181 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: '特定のイベントに関するリアルタイムの更新を手軽に受け取るためにWebhookを作成します。', create: 'Webhookを作成する', - events: { - post_register: '新しいアカウントを作成する', - post_sign_in: 'サインインする', - post_reset_password: 'パスワードをリセットする', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts index 59eadd01058..ba76cf57c95 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: '웹훅', subtitle: '특정 이벤트에 대한 실시간 업데이트를 쉽게 수신할 수 있는 웹훅을 생성하세요.', create: '웹훅 생성', - events: { - post_register: '새 계정 만들기', - post_sign_in: '로그인', - post_reset_password: '비밀번호 재설정', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts index 57eff4b5b9e..6a438b010ca 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Utwórz webhooki, aby bez wysiłku otrzymywać aktualizacje w czasie rzeczywistym dotyczące określonych zdarzeń.', create: 'Utwórz webhook', - events: { - post_register: 'Utwórz nowe konto', - post_sign_in: 'Zaloguj się', - post_reset_password: 'Zresetuj hasło', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts index c2eb6e31828..c4bd07a2858 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Crie ganchos da web para receber atualizações em tempo real sobre eventos específicos sem esforço.', create: 'Criar Webhook', - events: { - post_register: 'Criar nova conta', - post_sign_in: 'Entrar', - post_reset_password: 'Redefinir senha', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts index 6e10d9f1712..c9c68ae2127 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: 'Crie webhooks para receber atualizações em tempo real sobre eventos específicos.', create: 'Criar Webhook', - events: { - post_register: 'Criar nova conta', - post_sign_in: 'Entrar', - post_reset_password: 'Redefinir senha', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts index 5071f37d582..e26d245267e 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Создайте вебхуки, чтобы легко получать обновления в реальном времени относительно определенных событий.', create: 'Создать вебхук', - events: { - post_register: 'Создать новый аккаунт', - post_sign_in: 'Войти', - post_reset_password: 'Сбросить пароль', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts index 0ecdcb09fb5..32e7e2843f5 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/webhooks.ts @@ -4,11 +4,6 @@ const webhooks = { subtitle: 'Belirli olaylarla ilgili gerçek zamanlı güncellemeler almak için webhooklar oluşturun.', create: 'Webhook Oluştur', - events: { - post_register: 'Yeni hesap oluştur', - post_sign_in: 'Oturum açın', - post_reset_password: 'Parolayı sıfırla', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts index 17a51e62f8d..4497fc6cd19 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: '创建 Webhooks 以轻松接收有关特定事件的实时更新。', create: '创建 Webhook', - events: { - post_register: '创建新账户', - post_sign_in: '登录', - post_reset_password: '重置密码', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts index 83bdbacec54..74ec1629714 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: '創建 Webhooks,輕鬆地接收有關特定事件的實時更新。', create: '創建 Webhook', - events: { - post_register: '創建新帳戶', - post_sign_in: '登錄', - post_reset_password: '重置密碼', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts index ceacf885976..59aa90d2610 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/webhooks.ts @@ -3,11 +3,6 @@ const webhooks = { title: 'Webhooks', subtitle: '創建 Webhook 以輕鬆收到特定事件的即時更新。', create: '創建 Webhook', - events: { - post_register: '創建新帳戶', - post_sign_in: '登錄', - post_reset_password: '重置密碼', - }, schemas: { /** UNTRANSLATED */ interaction: 'User interaction', From 0303f965ccd4bf53df3cc5b5a6d4a511f4a94979 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 14 May 2024 11:29:18 +0800 Subject: [PATCH 05/11] fix(test): fix integration test fix integration test --- .../src/tests/console/webhooks/helpers.ts | 4 ++-- .../src/tests/console/webhooks/index.test.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/integration-tests/src/tests/console/webhooks/helpers.ts b/packages/integration-tests/src/tests/console/webhooks/helpers.ts index 9d38da29f59..bfdfb03bdaf 100644 --- a/packages/integration-tests/src/tests/console/webhooks/helpers.ts +++ b/packages/integration-tests/src/tests/console/webhooks/helpers.ts @@ -2,8 +2,8 @@ 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).toClick('span[class$=label]', { text: 'PostRegister' }); + await expect(page).toClick('span[class$=label]', { text: 'User.Updated' }); await expect(page).toFill('input[name=name]', 'hook_name'); await expect(page).toFill('input[name=url]', 'https://localhost/webhook'); await expect(page).toClick('button[type=submit]'); diff --git a/packages/integration-tests/src/tests/console/webhooks/index.test.ts b/packages/integration-tests/src/tests/console/webhooks/index.test.ts index 4573c52f8de..4ad99d4b880 100644 --- a/packages/integration-tests/src/tests/console/webhooks/index.test.ts +++ b/packages/integration-tests/src/tests/console/webhooks/index.test.ts @@ -1,13 +1,13 @@ import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js'; import { - goToAdminConsole, - expectToSaveChanges, - waitForToast, - expectToClickModalAction, - expectToClickDetailsPageOption, - expectModalWithTitle, expectConfirmModalAndAct, expectMainPageWithTitle, + expectModalWithTitle, + expectToClickDetailsPageOption, + expectToClickModalAction, + expectToSaveChanges, + goToAdminConsole, + waitForToast, } from '#src/ui-helpers/index.js'; import { appendPathname, dcls, expectNavigation } from '#src/utils.js'; @@ -63,8 +63,8 @@ describe('webhooks', () => { await expectNavigation(page.goto(appendPathname('/console/webhooks', logtoConsoleUrl).href)); 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).toClick('span[class$=label]', { text: 'PostRegister' }); + await expect(page).toClick('span[class$=label]', { text: 'User.Create' }); await expect(page).toFill('input[name=name]', 'hook_name'); await expect(page).toFill('input[name=url]', 'http://localhost/webhook'); await expect(page).toClick('button[type=submit]'); From 8b8e9455167919d353ad819cee1ee9851d2a9958 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 14 May 2024 14:29:56 +0800 Subject: [PATCH 06/11] feat(console): add devFeature guard add devFeature guard --- .../src/components/BasicWebhookForm/index.tsx | 16 ++++++++++------ .../pages/WebhookDetails/WebhookLogs/index.tsx | 7 ++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/console/src/components/BasicWebhookForm/index.tsx b/packages/console/src/components/BasicWebhookForm/index.tsx index f110c7b852c..b3d11c50379 100644 --- a/packages/console/src/components/BasicWebhookForm/index.tsx +++ b/packages/console/src/components/BasicWebhookForm/index.tsx @@ -2,6 +2,7 @@ import { type Hook, type HookConfig, type HookEvent } from '@logto/schemas'; import { Controller, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { isDevFeaturesEnabled } from '@/consts/env'; import { dataHookEventsLabel, interactionHookEvents, @@ -17,12 +18,15 @@ import { uriValidator } from '@/utils/validator'; import * as styles from './index.module.scss'; const hookEventGroups: Array> = [ - ...schemaGroupedDataHookEvents.map(([schema, events]) => ({ - title: dataHookEventsLabel[schema], - options: events.map((event) => ({ - value: event, - })), - })), + // TODO: Remove dev feature guard + ...(isDevFeaturesEnabled + ? schemaGroupedDataHookEvents.map(([schema, events]) => ({ + title: dataHookEventsLabel[schema], + options: events.map((event) => ({ + value: event, + })), + })) + : []), { title: 'webhooks.schemas.interaction', options: interactionHookEvents.map((event) => ({ diff --git a/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx b/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx index d89f41a843a..219a36d1c22 100644 --- a/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx +++ b/packages/console/src/pages/WebhookDetails/WebhookLogs/index.tsx @@ -8,6 +8,8 @@ import { z } from 'zod'; import EventSelector from '@/components/AuditLogTable/components/EventSelector'; import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder'; import { defaultPageSize } from '@/consts'; +import { isDevFeaturesEnabled } from '@/consts/env'; +import { interactionHookEvents } from '@/consts/webhooks'; import Table from '@/ds-components/Table'; import Tag from '@/ds-components/Tag'; import { type RequestError } from '@/hooks/use-api'; @@ -20,7 +22,10 @@ import { buildHookEventLogKey, getHookEventKey } from '../utils'; import * as styles from './index.module.scss'; -const hookLogEventOptions = hookEvents.map((event) => ({ +// TODO: Remove dev feature guard +const webhookEvents = isDevFeaturesEnabled ? hookEvents : interactionHookEvents; + +const hookLogEventOptions = webhookEvents.map((event) => ({ title: event, value: buildHookEventLogKey(event), })); From b095aee4125c3fb582525d57486f6f976e9189a1 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 14 May 2024 14:39:01 +0800 Subject: [PATCH 07/11] chore: add changeset add changeset --- .changeset/curvy-boxes-hide.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/curvy-boxes-hide.md diff --git a/.changeset/curvy-boxes-hide.md b/.changeset/curvy-boxes-hide.md new file mode 100644 index 00000000000..632fef6c1c0 --- /dev/null +++ b/.changeset/curvy-boxes-hide.md @@ -0,0 +1,12 @@ +--- +"@logto/console": patch +"@logto/phrases": patch +--- + +replace the i18n translated hook event label with the hook event value directly in the console + +- remove all the legacy interaction hook events i18n phrases +- replace the translated label with the hook event value directly in the console + - `Create new account` -> `PostRegister` + - `Sign in` -> `PostSignIn` + - `Reset password` -> `PostResetPassword` From d204171849cd057231302211b9a9fde364ceac30 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 14 May 2024 14:48:13 +0800 Subject: [PATCH 08/11] chore(console): remove the lint rule disable comment remove the lint rule disable comment --- packages/console/src/consts/env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/console/src/consts/env.ts b/packages/console/src/consts/env.ts index 6459febce95..888eb2d738f 100644 --- a/packages/console/src/consts/env.ts +++ b/packages/console/src/consts/env.ts @@ -3,6 +3,6 @@ import { yes } from '@silverhand/essentials'; const isProduction = process.env.NODE_ENV === 'production'; export const isCloud = yes(process.env.IS_CLOUD); export const adminEndpoint = process.env.ADMIN_ENDPOINT; -// eslint-disable-next-line import/no-unused-modules + export const isDevFeaturesEnabled = !isProduction || yes(process.env.DEV_FEATURES_ENABLED) || yes(process.env.INTEGRATION_TEST); From 032535a860fd8af1f6fbb4c9f3a5ea685929f8ed Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 15 May 2024 11:16:59 +0800 Subject: [PATCH 09/11] fix(test): fix the integartion tests fix the integration tests --- .../src/tests/api/hook/hook.trigger.interaction.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/integration-tests/src/tests/api/hook/hook.trigger.interaction.test.ts b/packages/integration-tests/src/tests/api/hook/hook.trigger.interaction.test.ts index 5d9fa08b8d2..7fe528e69c3 100644 --- a/packages/integration-tests/src/tests/api/hook/hook.trigger.interaction.test.ts +++ b/packages/integration-tests/src/tests/api/hook/hook.trigger.interaction.test.ts @@ -24,7 +24,7 @@ import { enableAllVerificationCodeSignInMethods, } from '#src/helpers/sign-in-experience.js'; import { UserApiTest, generateNewUserProfile } from '#src/helpers/user.js'; -import { generateEmail, generatePassword } from '#src/utils.js'; +import { generateEmail, generatePassword, waitFor } from '#src/utils.js'; import WebhookMockServer, { mockHookResponseGuard, verifySignature } from './WebhookMockServer.js'; @@ -47,6 +47,9 @@ const assertHookLogResult = async ( hookPayload?: Record; } ) => { + // Since the webhook request is async, we need to wait for a while to ensure the webhook response is received. + await waitFor(50); + const logs = await getWebhookRecentLogs( hookId, new URLSearchParams({ logKey: `TriggerHook.${event}`, page_size: '10' }) From 017b54d64092e3e56b2132d879e63c2d4bf63480 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Wed, 15 May 2024 11:37:19 +0800 Subject: [PATCH 10/11] fix(console): refine the code refine the code --- packages/console/src/consts/webhooks.ts | 13 +++++++++---- .../CategorizedCheckboxGroup/index.module.scss | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/console/src/consts/webhooks.ts b/packages/console/src/consts/webhooks.ts index 0aec6c9ffc6..d82b157d6ce 100644 --- a/packages/console/src/consts/webhooks.ts +++ b/packages/console/src/consts/webhooks.ts @@ -15,13 +15,19 @@ export const dataHookEventsLabel = Object.freeze({ [DataHookSchema.OrganizationScope]: 'webhooks.schemas.organization_scope', } satisfies Record); +export const interactionHookEvents = Object.values(InteractionHookEvent); + const dataHookEvents: DataHookEvent[] = hookEvents.filter( - (event): event is DataHookEvent => !(event in InteractionHookEvent) + // eslint-disable-next-line no-restricted-syntax + (event): event is DataHookEvent => !interactionHookEvents.includes(event as InteractionHookEvent) ); -const isDataHookSchema = (schema: string): schema is DataHookSchema => schema in DataHookSchema; +const isDataHookSchema = (schema: string): schema is DataHookSchema => + // eslint-disable-next-line no-restricted-syntax + Object.values(DataHookSchema).includes(schema as DataHookSchema); // Group DataHook events by schema +// TODO: Replace this using groupBy once Node v22 goes LTS const schemaGroupedDataHookEventsMap = dataHookEvents.reduce>( (eventGroup, event) => { const [schema] = event.split('.'); @@ -35,8 +41,7 @@ const schemaGroupedDataHookEventsMap = dataHookEvents.reduce Date: Wed, 15 May 2024 14:02:48 +0800 Subject: [PATCH 11/11] chore(console): refine comments refine comments --- packages/console/src/consts/webhooks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/console/src/consts/webhooks.ts b/packages/console/src/consts/webhooks.ts index d82b157d6ce..14674cb7c4a 100644 --- a/packages/console/src/consts/webhooks.ts +++ b/packages/console/src/consts/webhooks.ts @@ -27,7 +27,7 @@ const isDataHookSchema = (schema: string): schema is DataHookSchema => Object.values(DataHookSchema).includes(schema as DataHookSchema); // Group DataHook events by schema -// TODO: Replace this using groupBy once Node v22 goes LTS +// TODO: Replace this using `groupBy` once Node v22 goes LTS const schemaGroupedDataHookEventsMap = dataHookEvents.reduce>( (eventGroup, event) => { const [schema] = event.split('.'); @@ -41,7 +41,7 @@ const schemaGroupedDataHookEventsMap = dataHookEvents.reduce