diff --git a/apps/sensenet/src/components/content-list/content-list.tsx b/apps/sensenet/src/components/content-list/content-list.tsx index 30cfb9462..2d0dd1391 100644 --- a/apps/sensenet/src/components/content-list/content-list.tsx +++ b/apps/sensenet/src/components/content-list/content-list.tsx @@ -424,6 +424,21 @@ export const ContentList = (props: Co ) } return null + case 'SuccessfulCalls': + return ( + openContext(ev, fieldOptions.rowData)}> + { + openDialog({ + name: 'webhook-log', + props: { content: fieldOptions.rowData }, + }) + }} + cellData={fieldOptions.cellData} + textForLink=" (Click for details)" + /> + + ) default: break } diff --git a/apps/sensenet/src/components/dialogs/dialog-provider.tsx b/apps/sensenet/src/components/dialogs/dialog-provider.tsx index 4d5f299f9..1551aa069 100644 --- a/apps/sensenet/src/components/dialogs/dialog-provider.tsx +++ b/apps/sensenet/src/components/dialogs/dialog-provider.tsx @@ -17,6 +17,7 @@ import { RestoreProps, SaveQueryProps, UploadDialogProps, + WebhookLogDialogProps, } from '.' export type DialogWithProps = ( @@ -39,6 +40,7 @@ export type DialogWithProps = ( | { name: 'feedback' } | { name: 'change-password' } | { name: 'date-range-picker'; props: DateRangePickerProps } + | { name: 'webhook-log'; props: WebhookLogDialogProps } ) & { dialogProps?: Partial } type Action = { type: 'PUSH_DIALOG'; dialog: DialogWithProps } | { type: 'POP_DIALOG' } | { type: 'CLOSE_ALL_DIALOGS' } diff --git a/apps/sensenet/src/components/dialogs/dialogs.tsx b/apps/sensenet/src/components/dialogs/dialogs.tsx index 92a1b5c40..86f2b0bb4 100644 --- a/apps/sensenet/src/components/dialogs/dialogs.tsx +++ b/apps/sensenet/src/components/dialogs/dialogs.tsx @@ -22,6 +22,7 @@ const ContentPicker = lazy(() => import('./content-picker')) const Feedback = lazy(() => import('./feedback')) const ChangePasswordDialog = lazy(() => import('./change-password')) const DateRangePicker = lazy(() => import('./date-range-picker')) +const WebhookLog = lazy(() => import('./webhook-log/webhook-log')) function dialogRenderer(dialog: DialogWithProps) { switch (dialog.name) { @@ -63,6 +64,8 @@ function dialogRenderer(dialog: DialogWithProps) { return case 'date-range-picker': return + case 'webhook-log': + return default: return null } diff --git a/apps/sensenet/src/components/dialogs/index.ts b/apps/sensenet/src/components/dialogs/index.ts index fd1b9bae9..a16873271 100644 --- a/apps/sensenet/src/components/dialogs/index.ts +++ b/apps/sensenet/src/components/dialogs/index.ts @@ -17,3 +17,4 @@ export * from './permission-editor-dialog' export * from './reference-content-list' export * from './restore' export * from './save-query' +export * from './webhook-log' diff --git a/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table-row.tsx b/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table-row.tsx new file mode 100644 index 000000000..0734fceed --- /dev/null +++ b/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table-row.tsx @@ -0,0 +1,116 @@ +import { Box, Collapse, createStyles, makeStyles, TableCell, TableRow, Theme, useTheme } from '@material-ui/core' +import { FiberManualRecord } from '@material-ui/icons' +import React, { useState } from 'react' +import { useLocalization } from '../../../hooks' +import { useDateUtils } from '../../../hooks/use-date-utils' +import { WebhookStatInput } from './types' + +const useStyles = makeStyles((theme: Theme) => { + return createStyles({ + row: { + '& > *': { + borderBottom: 'unset', + }, + }, + icon: { + height: '20px', + width: '20px', + marginRight: '16px', + }, + cell: { + display: 'flex', + alignItems: 'center', + }, + actionCell: { + color: theme.palette.primary.main, + cursor: 'pointer', + textDecoration: 'underline', + }, + box: { + display: 'flex', + }, + title: { + color: theme.palette.primary.main, + paddingRight: '16px', + }, + }) +}) + +export function CollapsibleTableRow(props: { row: WebhookStatInput }) { + const classes = useStyles() + const theme = useTheme() + const localization = useLocalization().webhookLogDialog + const dateUtils = useDateUtils() + const [open, setOpen] = useState(false) + + return ( + + + + {dateUtils.formatDate(new Date(props.row.CreationTime), 'yyyy-MM-dd HH:mm aaa')} + + + + HTTP {props.row.ResponseStatusCode} + + setOpen(!open)}> + {localization.details} + + + + + + +
+
+ {localization.requestTime} + {dateUtils.formatDate(new Date(props.row.CreationTime), 'yyyy-MM-dd HH:mm aaa')} +
+
+ {localization.requestLength} + {props.row.RequestLength} +
+
+ {localization.webhookId} + {props.row.WebHookId} +
+
+ {localization.eventName} + {props.row.EventName} +
+
+
+
+ {localization.duration} + {props.row.Duration} +
+
+ {localization.responseLength} + {props.row.ResponseLength} +
+
+ {localization.contentId} + {props.row.ContentId} +
+
+ {localization.errorMessage} + {props.row.ErrorMessage} +
+
+
+
+
+
+
+ ) +} diff --git a/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table.tsx b/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table.tsx new file mode 100644 index 000000000..91c380896 --- /dev/null +++ b/apps/sensenet/src/components/dialogs/webhook-log/collapsible-table.tsx @@ -0,0 +1,19 @@ +import { Table, TableBody } from '@material-ui/core' +import React from 'react' +import { CollapsibleTableRow, WebhookStatInput } from '.' + +export interface CollapsibleTableProps { + webhooks: WebhookStatInput[] +} + +export function CollapsibleTable({ webhooks }: CollapsibleTableProps) { + return ( + + + {webhooks.map((webhookItem) => ( + + ))} + +
+ ) +} diff --git a/apps/sensenet/src/components/dialogs/webhook-log/index.ts b/apps/sensenet/src/components/dialogs/webhook-log/index.ts new file mode 100644 index 000000000..14401ef87 --- /dev/null +++ b/apps/sensenet/src/components/dialogs/webhook-log/index.ts @@ -0,0 +1,4 @@ +export * from './collapsible-table-row' +export * from './collapsible-table' +export * from './types' +export * from './webhook-log' diff --git a/apps/sensenet/src/components/dialogs/webhook-log/types.ts b/apps/sensenet/src/components/dialogs/webhook-log/types.ts new file mode 100644 index 000000000..e147c6329 --- /dev/null +++ b/apps/sensenet/src/components/dialogs/webhook-log/types.ts @@ -0,0 +1,12 @@ +export type WebhookStatInput = { + Url: string + CreationTime: string + Duration: string + RequestLength: number + ResponseLength: number + ResponseStatusCode: number + WebHookId: number | string + ContentId: number | string + EventName: string + ErrorMessage: string | null +} diff --git a/apps/sensenet/src/components/dialogs/webhook-log/webhook-log.tsx b/apps/sensenet/src/components/dialogs/webhook-log/webhook-log.tsx new file mode 100644 index 000000000..be018fd0a --- /dev/null +++ b/apps/sensenet/src/components/dialogs/webhook-log/webhook-log.tsx @@ -0,0 +1,67 @@ +import { WebhookSubscription } from '@sensenet/default-content-types' +import { useLogger, useRepository } from '@sensenet/hooks-react' +import { Button, DialogActions, DialogContent } from '@material-ui/core' +import React, { useEffect, useState } from 'react' +import { useGlobalStyles } from '../../../globalStyles' +import { useLocalization } from '../../../hooks' +import { useDialog } from '../dialog-provider' +import { DialogTitle } from '../dialog-title' +import { CollapsibleTable } from './collapsible-table' +import { WebhookStatInput } from '.' + +export type WebhookLogDialogProps = { + content: WebhookSubscription +} + +export const WebhookLogDialog: React.FunctionComponent = (props) => { + const { closeLastDialog } = useDialog() + const globalClasses = useGlobalStyles() + const localization = useLocalization() + const logger = useLogger('WebhookLog') + const repo = useRepository() + const [data, setData] = useState([]) + + useEffect(() => { + const getData = async () => { + try { + const response = await repo.executeAction({ + idOrPath: props.content.Path, + name: `GetWebHookUsageList`, + method: 'POST', + body: {}, + }) + console.log(response) + setData(response) + } catch (error) { + logger.error({ + message: error.message, + }) + } + } + + getData() + }, [logger, props.content.Path, repo]) + + return ( + <> + +
{props.content.WebHookUrl}
+
+ <> + + + + + + + + + ) +} + +export default WebhookLogDialog diff --git a/apps/sensenet/src/localization/default.ts b/apps/sensenet/src/localization/default.ts index 3e440feb9..a49eacb27 100644 --- a/apps/sensenet/src/localization/default.ts +++ b/apps/sensenet/src/localization/default.ts @@ -638,6 +638,18 @@ const values = { example: 'Example:', defaultPayload: 'Default payload', }, + webhookLogDialog: { + log: 'Log', + details: 'Details', + requestTime: 'Request time:', + duration: 'Duration:', + requestLength: 'Request length:', + responseLength: 'Response length', + webhookId: 'Webhook id:', + contentId: 'Content id:', + eventName: 'Event name:', + errorMessage: 'Error message:', + }, editor: {}, } diff --git a/apps/sensenet/src/localization/hungarian.ts b/apps/sensenet/src/localization/hungarian.ts index cd0082f34..2d4b7c44a 100644 --- a/apps/sensenet/src/localization/hungarian.ts +++ b/apps/sensenet/src/localization/hungarian.ts @@ -306,6 +306,18 @@ const values: Localization = { 'Saját payload bármilyen valid JSON lehet. Ha az alapértelmezett payload-ot szeretné használni, hagyja üresen ezt a mezőt.', example: 'Példa:', }, + webhookLogDialog: { + log: 'Napló', + details: 'Részletek', + requestTime: 'Kérés időpontja:', + duration: 'Válasz időtartama:', + requestLength: 'Kérés mérete:', + responseLength: 'Válasz mérete:', + webhookId: 'Webhook azonosító:', + contentId: 'Kontent azonosító:', + eventName: 'Esemény neve:', + errorMessage: 'Hibaüzenet:', + }, editor: { common: { cancel: 'Mégse', diff --git a/packages/sn-default-content-types/src/DefaultContentTypes.ts b/packages/sn-default-content-types/src/DefaultContentTypes.ts index b24ca0abc..b9c867fb0 100644 --- a/packages/sn-default-content-types/src/DefaultContentTypes.ts +++ b/packages/sn-default-content-types/src/DefaultContentTypes.ts @@ -758,3 +758,27 @@ export type Link = ListItem & { * Content list type for grouping external links */ export type LinkList = ContentList + +/** + * Defines an event and a 3rd party service that should be called when the event is fired. + */ +export type WebhookSubscription = GenericContent & { + /* Http method */ + WebHookHttpMethod: Enums.HttpMethod[] + /* Url */ + WebHookUrl: string + /* Triggers */ + WebHookFilter: string + /* HTTP headers */ + WebHookHeaders: string + /* Status */ + Enabled: boolean + + IsValid: boolean + + InvalidFields: string + /* % of successful calls */ + SuccessfulCalls: number + /* Custom payload */ + WebHookPayload: string +} diff --git a/packages/sn-default-content-types/src/Enums.ts b/packages/sn-default-content-types/src/Enums.ts index cfcca4aec..f86757498 100644 --- a/packages/sn-default-content-types/src/Enums.ts +++ b/packages/sn-default-content-types/src/Enums.ts @@ -102,3 +102,10 @@ export enum EventType { Meeting = 'Meeting', Demo = 'Demo', } +export enum HttpMethod { + GET = 'GET', + POST = 'POST', + PATCH = 'PATCH', + PUT = 'PUT', + DELETE = 'DELETE', +} diff --git a/packages/sn-list-controls-react/src/ContentList/CellTemplates/VirtualDefaultCell.tsx b/packages/sn-list-controls-react/src/ContentList/CellTemplates/VirtualDefaultCell.tsx index 0b7ee9cd0..4542ce282 100644 --- a/packages/sn-list-controls-react/src/ContentList/CellTemplates/VirtualDefaultCell.tsx +++ b/packages/sn-list-controls-react/src/ContentList/CellTemplates/VirtualDefaultCell.tsx @@ -1,27 +1,41 @@ import { GenericContent } from '@sensenet/default-content-types' -import { TableCell, Tooltip } from '@material-ui/core' -import React from 'react' +import { TableCell, Tooltip, useTheme } from '@material-ui/core' +import React, { MouseEventHandler } from 'react' export interface VirtualDefaultCellProps { cellData: T + onTextClick?: MouseEventHandler + textForLink?: string } -export const VirtualDefaultCell = (props: VirtualDefaultCellProps) => ( - - -
- {props.cellData?.toString()} -
-
-
-) +export const VirtualDefaultCell = (props: VirtualDefaultCellProps) => { + const theme = useTheme() + + return ( + + +
+ {props.cellData?.toString()} + {props.textForLink} +
+
+
+ ) +}