fix: TT-69

This commit is contained in:
anik-ghosh-au7 2022-07-15 22:12:08 +05:30
parent eabc943452
commit 8fc52d76dc
4 changed files with 495 additions and 147 deletions

View File

@ -20,7 +20,11 @@ import {
} from '@chakra-ui/react';
import { FaMinusCircle, FaPlus } from 'react-icons/fa';
import { useClient } from 'urql';
import { ArrayInputOperations } from '../constants';
import {
ArrayInputOperations,
WebhookInputDataFields,
WebhookInputHeaderFields,
} from '../constants';
import {
capitalizeFirstLetter,
validateEventName,
@ -28,62 +32,50 @@ import {
} from '../utils';
import { AddWebhook } from '../graphql/mutation';
enum INPUT_FIELDS {
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
enum HEADER_FIELDS {
KEY = 'key',
VALUE = 'value',
}
interface headersDataType {
[HEADER_FIELDS.KEY]: string;
[HEADER_FIELDS.VALUE]: string;
[WebhookInputHeaderFields.KEY]: string;
[WebhookInputHeaderFields.VALUE]: string;
}
interface headersValidatorDataType {
[HEADER_FIELDS.KEY]: boolean;
[HEADER_FIELDS.VALUE]: boolean;
[WebhookInputHeaderFields.KEY]: boolean;
[WebhookInputHeaderFields.VALUE]: boolean;
}
const initHeadersData: headersDataType = {
[HEADER_FIELDS.KEY]: '',
[HEADER_FIELDS.VALUE]: '',
[WebhookInputHeaderFields.KEY]: '',
[WebhookInputHeaderFields.VALUE]: '',
};
const initHeadersValidatorData: headersValidatorDataType = {
[HEADER_FIELDS.KEY]: true,
[HEADER_FIELDS.VALUE]: true,
[WebhookInputHeaderFields.KEY]: true,
[WebhookInputHeaderFields.VALUE]: true,
};
interface webhookDataType {
[INPUT_FIELDS.EVENT_NAME]: string;
[INPUT_FIELDS.ENDPOINT]: string;
[INPUT_FIELDS.ENABLED]: boolean;
[INPUT_FIELDS.HEADERS]: headersDataType[];
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]: headersDataType[];
}
interface validatorDataType {
[INPUT_FIELDS.EVENT_NAME]: boolean;
[INPUT_FIELDS.ENDPOINT]: boolean;
[INPUT_FIELDS.HEADERS]: headersValidatorDataType[];
[WebhookInputDataFields.EVENT_NAME]: boolean;
[WebhookInputDataFields.ENDPOINT]: boolean;
[WebhookInputDataFields.HEADERS]: headersValidatorDataType[];
}
const initWebhookData: webhookDataType = {
[INPUT_FIELDS.EVENT_NAME]: '',
[INPUT_FIELDS.ENDPOINT]: '',
[INPUT_FIELDS.ENABLED]: false,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersData }],
[WebhookInputDataFields.EVENT_NAME]: '',
[WebhookInputDataFields.ENDPOINT]: '',
[WebhookInputDataFields.ENABLED]: false,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
};
const initWebhookValidatorData: validatorDataType = {
[INPUT_FIELDS.EVENT_NAME]: true,
[INPUT_FIELDS.ENDPOINT]: true,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersValidatorData }],
[WebhookInputDataFields.EVENT_NAME]: true,
[WebhookInputDataFields.ENDPOINT]: true,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersValidatorData }],
};
const AddWebhookModal = () => {
@ -100,36 +92,38 @@ const AddWebhookModal = () => {
const inputChangehandler = (
inputType: string,
value: any,
headerInputType: string = HEADER_FIELDS.KEY,
headerInputType: string = WebhookInputHeaderFields.KEY,
headerIndex: number = 0
) => {
switch (inputType) {
case INPUT_FIELDS.EVENT_NAME:
case WebhookInputDataFields.EVENT_NAME:
setWebhook({ ...webhook, [inputType]: value });
setValidator({
...validator,
[INPUT_FIELDS.EVENT_NAME]: validateEventName(value),
[WebhookInputDataFields.EVENT_NAME]: validateEventName(value),
});
break;
case INPUT_FIELDS.ENDPOINT:
case WebhookInputDataFields.ENDPOINT:
setWebhook({ ...webhook, [inputType]: value });
setValidator({
...validator,
[INPUT_FIELDS.ENDPOINT]: validateURI(value),
[WebhookInputDataFields.ENDPOINT]: validateURI(value),
});
break;
case INPUT_FIELDS.ENABLED:
case WebhookInputDataFields.ENABLED:
setWebhook({ ...webhook, [inputType]: value });
break;
case INPUT_FIELDS.HEADERS:
const updatedHeaders: any = [...webhook[INPUT_FIELDS.HEADERS]];
case WebhookInputDataFields.HEADERS:
const updatedHeaders: any = [
...webhook[WebhookInputDataFields.HEADERS],
];
const updatedHeadersValidatorData: any = [
...validator[INPUT_FIELDS.HEADERS],
...validator[WebhookInputDataFields.HEADERS],
];
const otherHeaderInputType =
headerInputType === HEADER_FIELDS.KEY
? HEADER_FIELDS.VALUE
: HEADER_FIELDS.KEY;
headerInputType === WebhookInputHeaderFields.KEY
? WebhookInputHeaderFields.VALUE
: WebhookInputHeaderFields.KEY;
updatedHeaders[headerIndex][headerInputType] = value;
updatedHeadersValidatorData[headerIndex][headerInputType] =
value.length > 0
@ -154,34 +148,36 @@ const AddWebhookModal = () => {
case ArrayInputOperations.APPEND:
setWebhook({
...webhook,
[INPUT_FIELDS.HEADERS]: [
...(webhook?.[INPUT_FIELDS.HEADERS] || []),
[WebhookInputDataFields.HEADERS]: [
...(webhook?.[WebhookInputDataFields.HEADERS] || []),
{ ...initHeadersData },
],
});
setValidator({
...validator,
[INPUT_FIELDS.HEADERS]: [
...(validator?.[INPUT_FIELDS.HEADERS] || []),
[WebhookInputDataFields.HEADERS]: [
...(validator?.[WebhookInputDataFields.HEADERS] || []),
{ ...initHeadersValidatorData },
],
});
break;
case ArrayInputOperations.REMOVE:
if (webhook?.[INPUT_FIELDS.HEADERS]?.length) {
const updatedHeaders = [...webhook[INPUT_FIELDS.HEADERS]];
if (webhook?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeaders = [...webhook[WebhookInputDataFields.HEADERS]];
updatedHeaders.splice(index, 1);
setWebhook({
...webhook,
[INPUT_FIELDS.HEADERS]: updatedHeaders,
[WebhookInputDataFields.HEADERS]: updatedHeaders,
});
}
if (validator?.[INPUT_FIELDS.HEADERS]?.length) {
const updatedHeadersData = [...validator[INPUT_FIELDS.HEADERS]];
if (validator?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeadersData = [
...validator[WebhookInputDataFields.HEADERS],
];
updatedHeadersData.splice(index, 1);
setValidator({
...validator,
[INPUT_FIELDS.HEADERS]: updatedHeadersData,
[WebhookInputDataFields.HEADERS]: updatedHeadersData,
});
}
break;
@ -192,11 +188,11 @@ const AddWebhookModal = () => {
const validateData = () => {
return (
!loading &&
webhook[INPUT_FIELDS.EVENT_NAME].length > 0 &&
webhook[INPUT_FIELDS.ENDPOINT].length > 0 &&
validator[INPUT_FIELDS.EVENT_NAME] &&
validator[INPUT_FIELDS.ENDPOINT] &&
!validator[INPUT_FIELDS.HEADERS].some(
webhook[WebhookInputDataFields.EVENT_NAME].length > 0 &&
webhook[WebhookInputDataFields.ENDPOINT].length > 0 &&
validator[WebhookInputDataFields.EVENT_NAME] &&
validator[WebhookInputDataFields.ENDPOINT] &&
!validator[WebhookInputDataFields.HEADERS].some(
(headerData: headersValidatorDataType) =>
!headerData.key || !headerData.value
)
@ -205,15 +201,17 @@ const AddWebhookModal = () => {
const saveData = async () => {
if (!validateData()) return;
setLoading(true);
let { [INPUT_FIELDS.HEADERS]: _, ...params }: any = webhook;
if (
webhook[INPUT_FIELDS.HEADERS].length > 0 &&
webhook[INPUT_FIELDS.HEADERS][0][HEADER_FIELDS.KEY]
) {
const headers = webhook[INPUT_FIELDS.HEADERS].reduce((acc, data) => {
return { ...acc, [data.key]: data.value };
}, {});
params[INPUT_FIELDS.HEADERS] = headers;
let { [WebhookInputDataFields.HEADERS]: _, ...params }: any = webhook;
if (webhook[WebhookInputDataFields.HEADERS].length) {
const headers = webhook[WebhookInputDataFields.HEADERS].reduce(
(acc, data) => {
return data.key ? { ...acc, [data.key]: data.value } : acc;
},
{}
);
if (Object.keys(headers).length) {
params[WebhookInputDataFields.HEADERS] = headers;
}
}
const res = await client.mutation(AddWebhook, { params }).toPromise();
if (res.error) {
@ -234,7 +232,7 @@ const AddWebhookModal = () => {
});
setWebhook({
...initWebhookData,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersData }],
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
});
setValidator({ ...initWebhookValidatorData });
onClose();
@ -279,11 +277,11 @@ const AddWebhookModal = () => {
pr="4.5rem"
type="text"
placeholder="user.login"
value={webhook[INPUT_FIELDS.EVENT_NAME]}
isInvalid={!validator[INPUT_FIELDS.EVENT_NAME]}
value={webhook[WebhookInputDataFields.EVENT_NAME]}
isInvalid={!validator[WebhookInputDataFields.EVENT_NAME]}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.EVENT_NAME,
WebhookInputDataFields.EVENT_NAME,
e.currentTarget.value
)
}
@ -304,11 +302,11 @@ const AddWebhookModal = () => {
pr="4.5rem"
type="text"
placeholder="https://domain.com/webhook"
value={webhook[INPUT_FIELDS.ENDPOINT]}
isInvalid={!validator[INPUT_FIELDS.ENDPOINT]}
value={webhook[WebhookInputDataFields.ENDPOINT]}
isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.ENDPOINT,
WebhookInputDataFields.ENDPOINT,
e.currentTarget.value
)
}
@ -329,11 +327,11 @@ const AddWebhookModal = () => {
</Text>
<Switch
size="md"
isChecked={webhook[INPUT_FIELDS.ENABLED]}
isChecked={webhook[WebhookInputDataFields.ENABLED]}
onChange={() =>
inputChangehandler(
INPUT_FIELDS.ENABLED,
!webhook[INPUT_FIELDS.ENABLED]
WebhookInputDataFields.ENABLED,
!webhook[WebhookInputDataFields.ENABLED]
)
}
/>
@ -364,7 +362,8 @@ const AddWebhookModal = () => {
</Flex>
</Flex>
<Flex flexDirection="column" maxH={220} overflowY="scroll">
{webhook[INPUT_FIELDS.HEADERS]?.map((headerData, index) => (
{webhook[WebhookInputDataFields.HEADERS]?.map(
(headerData, index) => (
<Flex
key={`header-data-${index}`}
justifyContent="center"
@ -374,17 +373,17 @@ const AddWebhookModal = () => {
<Input
type="text"
placeholder="key"
value={headerData[HEADER_FIELDS.KEY]}
value={headerData[WebhookInputHeaderFields.KEY]}
isInvalid={
!validator[INPUT_FIELDS.HEADERS][index]?.[
HEADER_FIELDS.KEY
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.KEY
]
}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.HEADERS,
WebhookInputDataFields.HEADERS,
e.target.value,
HEADER_FIELDS.KEY,
WebhookInputHeaderFields.KEY,
index
)
}
@ -397,17 +396,17 @@ const AddWebhookModal = () => {
<Input
type="text"
placeholder="value"
value={headerData[HEADER_FIELDS.VALUE]}
value={headerData[WebhookInputHeaderFields.VALUE]}
isInvalid={
!validator[INPUT_FIELDS.HEADERS][index]?.[
HEADER_FIELDS.VALUE
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.VALUE
]
}
onChange={(e) =>
inputChangehandler(
INPUT_FIELDS.HEADERS,
WebhookInputDataFields.HEADERS,
e.target.value,
HEADER_FIELDS.VALUE,
WebhookInputHeaderFields.VALUE,
index
)
}
@ -428,7 +427,8 @@ const AddWebhookModal = () => {
</InputRightElement>
</InputGroup>
</Flex>
))}
)
)}
</Flex>
</Flex>
</ModalBody>

View File

@ -153,3 +153,18 @@ export const envSubViews = {
ADMIN_SECRET: 'admin-secret',
DB_CRED: 'db-cred',
};
export enum WebhookInputDataFields {
ID = 'id',
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
export enum WebhookInputHeaderFields {
KEY = 'key',
VALUE = 'value',
}
export const pageLimits: number[] = [5, 10, 15];

View File

@ -101,3 +101,23 @@ export const EmailVerificationQuery = `
}
}
`;
export const WebhooksDataQuery = `
query getWebhooksData {
_webhooks{
webhooks{
id
event_name
endpoint
enabled
headers
}
pagination{
limit
page
offset
total
}
}
}
`;

View File

@ -1,8 +1,109 @@
import React from 'react';
import { Box, Flex, Text } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import { useClient } from 'urql';
import {
Box,
Button,
Center,
Flex,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Select,
Spinner,
Table,
TableCaption,
Tag,
Tbody,
Td,
Text,
Th,
Thead,
Tooltip,
Tr,
} from '@chakra-ui/react';
import {
FaAngleDoubleLeft,
FaAngleDoubleRight,
FaAngleDown,
FaAngleLeft,
FaAngleRight,
FaExclamationCircle,
} from 'react-icons/fa';
import AddWebhookModal from '../components/AddWebhookModal';
import { pageLimits, WebhookInputDataFields } from '../constants';
import { WebhooksDataQuery } from '../graphql/queries';
interface paginationPropTypes {
limit: number;
page: number;
offset: number;
total: number;
maxPages: number;
}
interface webhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
}
const Webhooks = () => {
const client = useClient();
const [loading, setLoading] = useState<boolean>(false);
const [webhookData, setWebhookData] = useState<webhookDataTypes[]>([]);
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
limit: 5,
page: 1,
offset: 0,
total: 0,
maxPages: 1,
});
const getMaxPages = (pagination: paginationPropTypes) => {
const { limit, total } = pagination;
if (total > 1) {
return total % limit === 0
? total / limit
: parseInt(`${total / limit}`) + 1;
} else return 1;
};
const fetchWebookData = async () => {
setLoading(true);
const res = await client.query(WebhooksDataQuery).toPromise();
if (res.data?._webhooks) {
const { pagination, webhooks } = res.data?._webhooks;
const maxPages = getMaxPages(pagination);
if (webhooks?.length) {
setWebhookData(webhooks);
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
} else {
if (paginationProps.page !== 1) {
setPaginationProps({
...paginationProps,
...pagination,
maxPages,
page: 1,
});
}
}
}
setLoading(false);
};
useEffect(() => {
fetchWebookData();
}, []);
const paginationHandler = (value: Record<string, number>) => {
setPaginationProps({ ...paginationProps, ...value });
};
console.log('webhookData ==>> ', webhookData);
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
@ -11,6 +112,218 @@ const Webhooks = () => {
</Text>
<AddWebhookModal />
</Flex>
{!loading ? (
webhookData.length ? (
<Table variant="simple">
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Endpoint</Th>
<Th>Enabled</Th>
<Th>Headers</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{webhookData.map((webhook: webhookDataTypes, index: number) => (
<Tr
key={webhook[WebhookInputDataFields.ID]}
style={{ fontSize: 14 }}
>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_NAME]}
</Td>
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={
webhook[WebhookInputDataFields.ENABLED]
? 'green'
: 'yellow'
}
>
{webhook[WebhookInputDataFields.ENABLED].toString()}
</Tag>
</Td>
<Td>
{
Object.keys(webhook[WebhookInputDataFields.HEADERS] || {})
?.length
}
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
<MenuItem onClick={() => {}}>Edit</MenuItem>
<MenuItem onClick={() => {}}>Delete</MenuItem>
<MenuItem onClick={() => {}}>View Logs</MenuItem>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{pageLimits.map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : (
<Flex
flexDirection="column"
minH="25vh"
justifyContent="center"
alignItems="center"
>
<Center w="50px" marginRight="1.5%">
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
>
No Data
</Text>
</Flex>
)
) : (
<Center minH="25vh">
<Spinner />
</Center>
)}
</Box>
);
};